Skip to content

Commit

Permalink
Merge branch 'master' of github.com:elastic/kibana into security-rule…
Browse files Browse the repository at this point in the history
…-type-threshold
  • Loading branch information
madirey committed Sep 8, 2021
2 parents 3ae81a7 + a4828ba commit bb739d4
Show file tree
Hide file tree
Showing 96 changed files with 1,742 additions and 480 deletions.
106 changes: 106 additions & 0 deletions dev_docs/key_concepts/performance.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
id: kibDevPerformance
slug: /kibana-dev-docs/performance
title: Performance
summary: Performance tips for Kibana development.
date: 2021-09-02
tags: ['kibana', 'onboarding', 'dev', 'performance']
---

## Keep Kibana fast

*tl;dr*: Load as much code lazily as possible. Everyone loves snappy
applications with a responsive UI and hates spinners. Users deserve the
best experience whether they run Kibana locally or
in the cloud, regardless of their hardware and environment.

There are 2 main aspects of the perceived speed of an application: loading time
and responsiveness to user actions. Kibana loads and bootstraps *all*
the plugins whenever a user lands on any page. It means that
every new application affects the overall _loading performance_, as plugin code is
loaded _eagerly_ to initialize the plugin and provide plugin API to dependent
plugins.

However, it’s usually not necessary that the whole plugin code should be loaded
and initialized at once. The plugin could keep on loading code covering API functionality
on Kibana bootstrap, but load UI related code lazily on-demand, when an
application page or management section is mounted.
Always prefer to import UI root components lazily when possible (such as in `mount`
handlers). Even if their size may seem negligible, they are likely using
some heavy-weight libraries that will also be removed from the initial
plugin bundle, therefore, reducing its size by a significant amount.

```ts
import type { Plugin, CoreSetup, AppMountParameters } from 'kibana/public';
export class MyPlugin implements Plugin<MyPluginSetup> {
setup(core: CoreSetup, plugins: SetupDeps) {
core.application.register({
id: 'app',
title: 'My app',
async mount(params: AppMountParameters) {
const { mountApp } = await import('./app/mount_app');
return mountApp(await core.getStartServices(), params);
},
});
plugins.management.sections.section.kibana.registerApp({
id: 'app',
title: 'My app',
order: 1,
async mount(params) {
const { mountManagementSection } = await import('./app/mount_management_section');
return mountManagementSection(coreSetup, params);
},
});
return {
doSomething() {},
};
}
}
```

### Understanding plugin bundle size

Kibana Platform plugins are pre-built with `@kbn/optimizer`
and distributed as package artifacts. This means that it is no
longer necessary for us to include the `optimizer` in the
distributable version of Kibana Every plugin artifact contains all
plugin dependencies required to run the plugin, except some
stateful dependencies shared across plugin bundles via
`@kbn/ui-shared-deps`. This means that plugin artifacts _tend to
be larger_ than they were in the legacy platform. To understand the
current size of your plugin artifact, run `@kbn/optimizer` with:

```bash
node scripts/build_kibana_platform_plugins.js --dist --profile --focus=my_plugin
```

and check the output in the `target` sub-folder of your plugin folder:

```bash
ls -lh plugins/my_plugin/target/public/
# output
# an async chunk loaded on demand
... 262K 0.plugin.js
# eagerly loaded chunk
... 50K my_plugin.plugin.js
```

You might see at least one js bundle - `my_plugin.plugin.js`. This is
the _only_ artifact loaded by Kibana during bootstrap in the
browser. The rule of thumb is to keep its size as small as possible.
Other lazily loaded parts of your plugin will be present in the same folder as
separate chunks under `{number}.myplugin.js` names. If you want to
investigate what your plugin bundle consists of, you need to run
`@kbn/optimizer` with `--profile` flag to generate a
[webpack stats file](https://webpack.js.org/api/stats/).

```bash
node scripts/build_kibana_platform_plugins.js --dist --no-examples --profile
```

Many OSS tools allow you to analyze the generated stats file:

* [An official tool](https://webpack.github.io/analyse/#modules) from
Webpack authors
* [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/)
* [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer)
4 changes: 2 additions & 2 deletions docs/maps/maps-getting-started.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ and lighter shades will symbolize countries with less traffic.

. In **Statistics source**, set:
** **Index pattern** to **kibana_sample_data_logs**
** **Join field** to **geo.src**
** **Join field** to **geo.dest**

. Click **Add layer**.

. In **Layer settings**, set:

** **Name** to `Total Requests by Country`
** **Name** to `Total Requests by Destination`
** **Opacity** to 50%

. Add a Tooltip field:
Expand Down
5 changes: 1 addition & 4 deletions src/dev/build/tasks/create_empty_dirs_and_files_task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ export const CreateEmptyDirsAndFiles: Task = {
description: 'Creating some empty directories and files to prevent file-permission issues',

async run(config, log, build) {
await Promise.all([
mkdirp(build.resolvePath('plugins')),
mkdirp(build.resolvePath('data/optimize')),
]);
await Promise.all([mkdirp(build.resolvePath('plugins')), mkdirp(build.resolvePath('data'))]);
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export const useDashboardAppState = ({
savedDashboards,
kbnUrlStateStorage,
initializerContext,
savedObjectsTagging,
isEmbeddedExternally,
dashboardCapabilities,
dispatchDashboardStateChange,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe('getStateDefaults', () => {
"default_column",
],
"filters": undefined,
"hideChart": undefined,
"index": "index-pattern-with-timefield-id",
"interval": "auto",
"query": undefined,
Expand Down Expand Up @@ -54,6 +55,7 @@ describe('getStateDefaults', () => {
"default_column",
],
"filters": undefined,
"hideChart": undefined,
"index": "the-index-pattern-id",
"interval": "auto",
"query": undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function getStateDefaults({
index: indexPattern!.id,
interval: 'auto',
filters: cloneDeep(searchSource.getOwnField('filter')),
hideChart: undefined,
} as AppState;
if (savedSearch.grid) {
defaultState.grid = savedSearch.grid;
Expand Down
1 change: 1 addition & 0 deletions src/plugins/embeddable/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"name": "App Services",
"githubTeam": "kibana-app-services"
},
"description": "Adds embeddables service to Kibana",
"requiredPlugins": ["inspector", "uiActions"],
"extraPublicDirs": ["public/lib/test_samples", "common"],
"requiredBundles": ["savedObjects", "kibanaReact", "kibanaUtils"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ interface HelloWorldContainerInput extends ContainerInput {
}

interface HelloWorldContainerOptions {
getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'];
panelComponent: EmbeddableStart['EmbeddablePanel'];
getEmbeddableFactory?: EmbeddableStart['getEmbeddableFactory'];
panelComponent?: EmbeddableStart['EmbeddablePanel'];
}

export class HelloWorldContainer extends Container<InheritedInput, HelloWorldContainerInput> {
Expand All @@ -42,7 +42,7 @@ export class HelloWorldContainer extends Container<InheritedInput, HelloWorldCon
input: ContainerInput<{ firstName: string; lastName: string }>,
private readonly options: HelloWorldContainerOptions
) {
super(input, { embeddableLoaded: {} }, options.getEmbeddableFactory);
super(input, { embeddableLoaded: {} }, options.getEmbeddableFactory || (() => undefined));
}

public getInheritedInput(id: string) {
Expand All @@ -56,10 +56,14 @@ export class HelloWorldContainer extends Container<InheritedInput, HelloWorldCon
public render(node: HTMLElement) {
ReactDOM.render(
<I18nProvider>
<HelloWorldContainerComponent
container={this}
panelComponent={this.options.panelComponent}
/>
{this.options.panelComponent ? (
<HelloWorldContainerComponent
container={this}
panelComponent={this.options.panelComponent}
/>
) : (
<div>Panel component not provided.</div>
)}
</I18nProvider>,
node
);
Expand Down
1 change: 1 addition & 0 deletions src/plugins/expressions/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"name": "App Services",
"githubTeam": "kibana-app-services"
},
"description": "Adds expression runtime to Kibana",
"extraPublicDirs": ["common", "common/fonts"],
"requiredBundles": ["kibanaUtils", "inspector"]
}
Binary file not shown.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/plugins/share/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"name": "App Services",
"githubTeam": "kibana-app-services"
},
"description": "Adds URL Service and sharing capabilities to Kibana",
"requiredBundles": ["kibanaUtils"],
"optionalPlugins": ["securityOss"]
}
1 change: 1 addition & 0 deletions src/plugins/ui_actions/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"name": "App Services",
"githubTeam": "kibana-app-services"
},
"description": "Adds UI Actions service to Kibana",
"requiredBundles": ["kibanaUtils", "kibanaReact"]
}
2 changes: 2 additions & 0 deletions test/functional/apps/getting_started/_shakespeare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {

after(async () => {
await security.testUser.restoreDefaults();
await esArchiver.unload('test/functional/fixtures/es_archiver/getting_started/shakespeare');
await kibanaServer.uiSettings.replace({});
});

it('should create shakespeare index pattern', async function () {
Expand Down
11 changes: 9 additions & 2 deletions test/functional/apps/home/_navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common', 'header', 'home', 'timePicker']);
const appsMenu = getService('appsMenu');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');

describe('Kibana browser back navigation should work', function describeIndexTests() {
before(async () => {
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/discover');
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
await esArchiver.load('test/functional/fixtures/es_archiver/logstash_functional');
await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' });
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
});

after(async () => {
await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover');
await kibanaServer.uiSettings.replace({});
});

it('detect navigate back issues', async () => {
Expand Down
20 changes: 13 additions & 7 deletions test/functional/page_objects/dashboard_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,11 @@ export class DashboardPageObject extends FtrService {
}

public async clickQuickSave() {
await this.expectQuickSaveButtonEnabled();
this.log.debug('clickQuickSave');
await this.testSubjects.click('dashboardQuickSaveMenuItem');
await this.retry.try(async () => {
await this.expectQuickSaveButtonEnabled();
this.log.debug('clickQuickSave');
await this.testSubjects.click('dashboardQuickSaveMenuItem');
});
}

public async clickNewDashboard(continueEditing = false) {
Expand Down Expand Up @@ -392,10 +394,11 @@ export class DashboardPageObject extends FtrService {
*/
public async saveDashboard(
dashboardName: string,
saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true, exitFromEditMode: true }
saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true, exitFromEditMode: true },
clickMenuItem = true
) {
await this.retry.try(async () => {
await this.enterDashboardTitleAndClickSave(dashboardName, saveOptions);
await this.enterDashboardTitleAndClickSave(dashboardName, saveOptions, clickMenuItem);

if (saveOptions.needsConfirm) {
await this.ensureDuplicateTitleCallout();
Expand Down Expand Up @@ -435,9 +438,12 @@ export class DashboardPageObject extends FtrService {
*/
public async enterDashboardTitleAndClickSave(
dashboardTitle: string,
saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true }
saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true },
clickMenuItem = true
) {
await this.testSubjects.click('dashboardSaveMenuItem');
if (clickMenuItem) {
await this.testSubjects.click('dashboardSaveMenuItem');
}
const modalDialog = await this.testSubjects.find('savedObjectSaveModal');

this.log.debug('entering new title');
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/apm/dev_docs/local_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ To access an elasticsearch instance that has live data you have two options:

#### A. Connect to Elasticsearch on Cloud (internal devs only)

Find the credentials for the cluster [here](https://github.com/elastic/apm-dev/blob/master/docs/credentials/apm-ui-clusters.md#apmelstcco)
Find the credentials for the cluster [here](https://github.com/elastic/observability-dev/blob/master/docs/observability-clusters.md)

#### B. Start Elastic Stack and APM data generators

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/drilldowns/url_drilldown/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"name": "App Services",
"githubTeam": "kibana-app-services"
},
"description": "Adds drilldown implementations to Kibana",
"requiredPlugins": ["embeddable", "uiActions", "uiActionsEnhanced"],
"requiredBundles": ["kibanaUtils", "kibanaReact"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ describe('UrlDrilldown', () => {
indexPatterns: [{ id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }],
}
);
const data: any = {
const data = {
data: [
createPoint({ field: 'field0', value: 'value0' }),
createPoint({ field: 'field1', value: 'value1' }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface ContextValues {
panel: PanelValues;
}

function hasSavedObjectId(obj: Record<string, any>): obj is { savedObjectId: string } {
function hasSavedObjectId(obj: Record<string, unknown>): obj is { savedObjectId: string } {
return 'savedObjectId' in obj && typeof obj.savedObjectId === 'string';
}

Expand All @@ -64,12 +64,13 @@ function hasSavedObjectId(obj: Record<string, any>): obj is { savedObjectId: str
*/
function getIndexPatternIds(output: EmbeddableOutput): string[] {
function hasIndexPatterns(
_output: Record<string, any>
_output: unknown
): _output is { indexPatterns: Array<{ id?: string }> } {
return (
'indexPatterns' in _output &&
Array.isArray(_output.indexPatterns) &&
_output.indexPatterns.length > 0
typeof _output === 'object' &&
!!_output &&
Array.isArray((_output as { indexPatterns: unknown[] }).indexPatterns) &&
(_output as { indexPatterns: Array<{ id?: string }> }).indexPatterns.length > 0
);
}
return hasIndexPatterns(output)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ describe('VALUE_CLICK_TRIGGER', () => {

describe('handles undefined, null or missing values', () => {
test('undefined or missing values are removed from the result scope', () => {
const point = createPoint({ field: undefined } as any);
const point = createPoint(({ field: undefined } as unknown) as {
field: string;
value: string | null | number | boolean;
});
const eventScope = getEventScopeValues({
data: { data: [point] },
}) as ValueClickTriggerEventScope;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const toPrimitiveOrUndefined = (v: unknown): Primitive | undefined => {
return String(v);
};

export const deleteUndefinedKeys = <T extends Record<string, any>>(obj: T): T => {
export const deleteUndefinedKeys = <T extends Record<string, unknown>>(obj: T): T => {
Object.keys(obj).forEach((key) => {
if (obj[key] === undefined) {
delete obj[key];
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/embeddable_enhanced/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"name": "App Services",
"githubTeam": "kibana-app-services"
},
"description": "Extends embeddable plugin with more functionality",
"requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"]
}
Loading

0 comments on commit bb739d4

Please sign in to comment.