Skip to content

Commit

Permalink
Add Inspector feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Roes committed May 9, 2018
1 parent c8b8027 commit 52fcd66
Show file tree
Hide file tree
Showing 56 changed files with 2,264 additions and 90 deletions.
1 change: 1 addition & 0 deletions docs/development/plugin/development-uiexports.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ An aggregate list of available UiExport types:
| hacks | Any module that should be included in every application
| visTypes | Modules that register providers with the `ui/registry/vis_types` registry.
| fieldFormats | Modules that register providers with the `ui/registry/field_formats` registry.
| inspectorViews | Modules that register custom inspector views via the `viewRegistry` in `ui/inspector`.
| spyModes | Modules that register providers with the `ui/registry/spy_modes` registry.
| chromeNavControls | Modules that register providers with the `ui/registry/chrome_nav_controls` registry.
| navbarExtensions | Modules that register providers with the `ui/registry/navbar_extensions` registry.
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,11 @@
"proxy-from-env": "1.0.0",
"querystring-browser": "1.0.4",
"raw-loader": "0.5.1",
"react": "^16.2.0",
"react": "^16.3.0",
"react-addons-shallow-compare": "15.6.2",
"react-anything-sortable": "^1.7.4",
"react-color": "^2.13.8",
"react-dom": "^16.2.0",
"react-dom": "^16.3.0",
"react-grid-layout": "^0.16.2",
"react-input-range": "^1.3.0",
"react-markdown": "^3.1.4",
Expand Down
18 changes: 0 additions & 18 deletions src/core_plugins/dev_mode/index.js

This file was deleted.

4 changes: 0 additions & 4 deletions src/core_plugins/dev_mode/package.json

This file was deleted.

23 changes: 0 additions & 23 deletions src/core_plugins/dev_mode/public/vis_debug_spy_panel.html

This file was deleted.

14 changes: 0 additions & 14 deletions src/core_plugins/dev_mode/public/vis_debug_spy_panel.js

This file was deleted.

1 change: 1 addition & 0 deletions src/core_plugins/kibana/public/kibana.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import 'uiExports/managementSections';
import 'uiExports/devTools';
import 'uiExports/docViews';
import 'uiExports/embeddableFactories';
import 'uiExports/inspectorViews';

import 'ui/autoload/all';
import './home';
Expand Down
14 changes: 14 additions & 0 deletions src/core_plugins/kibana/public/visualize/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,20 @@ function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courie
description: 'Share Visualization',
template: require('plugins/kibana/visualize/editor/panels/share.html'),
testId: 'visualizeShareButton',
}, {
key: 'inspector',
description: 'Open Inspector for visualization',
disableButton() {
return !vis.hasInspector();
},
run() {
vis.openInspector().bindToAngularScope($scope);
},
tooltip() {
if (!vis.hasInspector()) {
return 'This visualization doesn\'t support any inspectors.';
}
}
}, {
key: 'refresh',
description: 'Refresh',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { PersistedState } from 'ui/persisted_state';
import { Embeddable } from 'ui/embeddable';
import chrome from 'ui/chrome';
import _ from 'lodash';

export class VisualizeEmbeddable extends Embeddable {
Expand Down Expand Up @@ -43,9 +42,6 @@ export class VisualizeEmbeddable extends Embeddable {
append: true,
timeRange: this.timeRange,
cssClass: `panel-content panel-content--fullWidth`,
// The chrome is permanently hidden in "embed mode" in which case we don't want to show the spy pane, since
// we deem that situation to be more public facing and want to hide more detailed information.
showSpyPanel: !chrome.getIsChromePermanentlyHidden(),
dataAttrs: {
'shared-item': '',
title: this.panelTitle,
Expand Down
1 change: 0 additions & 1 deletion src/dev/build/tasks/copy_source_task.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export const CopySourceTask = {
'!src/**/__tests__/**',
'!src/test_utils/**',
'!src/fixtures/**',
'!src/core_plugins/dev_mode/**',
'!src/core_plugins/tests_bundle/**',
'!src/core_plugins/testbed/**',
'!src/core_plugins/console/public/tests/**',
Expand Down
17 changes: 16 additions & 1 deletion src/ui/public/agg_types/buckets/terms.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Schemas } from '../../vis/editors/default/schemas';
import { createFilterTerms } from './create_filter/terms';
import orderAggTemplate from '../controls/order_agg.html';
import orderAndSizeTemplate from '../controls/order_and_size.html';
import otherBucketTemplate from 'ui/agg_types/controls/other_bucket.html';
import otherBucketTemplate from '../controls/other_bucket.html';

import { getRequestInspectorStats, getResponseInspectorStats } from '../../courier/utils/courier_inspector_utils';
import { buildOtherBucketAgg, mergeOtherBucketAggResponse, updateMissingBucket } from './_terms_other_bucket_helper';
import { toastNotifications } from '../../notify';

Expand Down Expand Up @@ -59,7 +61,20 @@ export const termsBucketAgg = new BucketAggType({
if (aggConfig.params.otherBucket) {
const filterAgg = buildOtherBucketAgg(aggConfigs, aggConfig, resp);
nestedSearchSource.set('aggs', filterAgg);

const request = aggConfigs.vis.API.inspectorAdapters.requests.start('Other Bucket request', {
description: `This request counts how much documents aren't included in the
actual data to build the "Other" bucket from that information.`
});
nestedSearchSource.getSearchRequestBody().then(body => {
request.json(body);
});
request.stats(getRequestInspectorStats(nestedSearchSource));

const response = await nestedSearchSource.fetchAsRejectablePromise();
request
.stats(getResponseInspectorStats(nestedSearchSource, response))
.ok({ json: response });
resp = mergeOtherBucketAggResponse(aggConfigs, resp, response, aggConfig, filterAgg());
}
if (aggConfig.params.missingBucket) {
Expand Down
54 changes: 54 additions & 0 deletions src/ui/public/courier/utils/courier_inspector_utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* This function collects statistics from a SearchSource and a response
* for the usage in the inspector stats panel. Pass in a searchSource and a response
* and the returned object can be passed to the `stats` method of the request
* logger.
*/
function getRequestInspectorStats(searchSource) {
const stats = {};
const index = searchSource.get('index');

if (index) {
stats['Index Pattern Title'] = {
value: index.title,
description: 'The index pattern against which this query was executed.',
};
stats ['Index Pattern ID'] = {
value: index.id,
description: 'The ID of the saved index pattern object in the .kibana index.',
};
}

return stats;
}

function getResponseInspectorStats(searchSource, resp) {
const lastRequest = searchSource.history && searchSource.history[searchSource.history.length - 1];
const stats = {};

if (resp && resp.took) {
stats['Query Time'] = {
value: `${resp.took}ms`,
description: `The time it took Elasticsearch to process the query.
This does not include the time it takes to send the request to Elasticsearch
or parse it in the browser.`,
};
}

if (resp && resp.hits) {
stats.Hits = {
value: `${resp.hits.total}`,
description: 'The total number of documents that matched the query.',
};
}

if (lastRequest && (lastRequest.ms === 0 || lastRequest.ms)) {
stats['Request time'] = {
value: `${lastRequest.ms}ms`,
};
}

return stats;
}

export { getRequestInspectorStats, getResponseInspectorStats };
127 changes: 127 additions & 0 deletions src/ui/public/inspector/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Inspector

The inspector is a contextual tool to gain insights into different elements
in Kibana, e.g. visualizations. It has the form of a flyout panel.

## Inspector Views

The "Inspector Panel" can have multiple so called "Inspector Views" inside of it.
These views are used to gain different information into the element you are inspecting.
There is a request inspector view to gain information in the requests done for this
element or a data inspector view to inspect the underlying data. Whether or not
a specific view is available depends on the used adapters.

## Inspector Adapters

Since the Inspector panel itself is not tight to a specific type of elements (visualizations,
saved searches, etc.), everything you need to open the inspector is a collection
of so called inspector adapters. A single adapter can be any type of JavaScript class.

Most likely an adapter offers some kind of logging capabilities for the element, that
uses it e.g. the request adapter allows element (like visualizations) to log requests
they make.

The corresponding inspector view will then use the information inside the adapter
to present the data in the panel. That concept allows different types of elements
to use the Inspector panel, while they can use completely or partial different adapters
and inspector views than other elements.

For example a visualization could provide the request and data adapter while a saved
search could only provide the request adapter and a Vega visualization could additionally
provide a Vega adapter.

There is no 1 to 1 relationship between adapters and views. An adapter could be used
by multiple views and a view can use data from multiple adapters. It's up to the
view to decide whether or not it wants to be shown for a given adapters list.

## Develop custom inspectors

You can extend the inspector panel by adding custom inspector views and inspector
adapters via a plugin.

### Develop inspector views

To develop custom inspector views you should first register your file via `uiExports`
in your plugin config:

```js
export default (kibana) => {
return new kibana.Plugin({
uiExports: {
inspectorViews: [ 'plugins/your_plugin/custom_view' ],
}
});
};
```

Within the `custom_view.js` file in your `public` folder, you can define your
inspector view as follows:

```js
import React from 'react';
import { InspectorView, viewRegistry } from 'ui/inspector';

function MyInspectorComponent(props) {
// props.adapters is the object of all adapters and may vary depending
// on who and where this inspector was opened. You should check for all
// adapters you need, in the below shouldShow method, before accessing
// them here.
return (
<InspectorView>
{ /* Always use InspectorView as the wrapping element! */ }
</InspectorView>
);
}

const MyLittleInspectorView = {
// Title shown to select this view
title: 'Display Name',
// An icon id from the EUI icon list
icon: 'iconName',
// An order to sort the views (lower means first)
order: 10,
// An additional helptext, that wil
help: `And additional help text, that will be shown in the inspector help.`,
shouldShow(adapters) {
// Only show if `someAdapter` is available. Make sure to check for
// all adapters that you want to access in your view later on and
// any additional condition you want to be true to be shown.
return adapters.someAdapter;
},
// A React component, that will be used for rendering
component: MyInspectorComponent
};

viewRegistry.register(MyLittleInspectorView);
```

### Develop custom adapters

An inspector adapter is just a plain JavaScript class, that can e.g. be attached
to custom visualization types, so an inspector view can show additional information for this
visualization.

To add additional adapters to your visualization type, use the `inspectorAdapters.custom`
object when defining the visualization type:

```js
class MyCustomInspectorAdapter {
// ....
}

// inside your visualization type description (usually passed to VisFactory.create...Type)
{
// ...
inspectorAdapters: {
custom: {
someAdaoter: MyCustomInspectorAdapter
}
}
}
```

An instance of MyCustomInspectorAdapter will now be available on each visualization
of that type and can be accessed via `vis.API.inspectorAdapters.someInspector`.

Custom inspector views can now check for the presence of `adapters.someAdapter`
in their `shouldShow` method and use this adapter in their component.
16 changes: 16 additions & 0 deletions src/ui/public/inspector/adapters/data_adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import EventEmitter from 'events';

class DataAdapter extends EventEmitter {

setTabularLoader(callback) {
this._tabular = callback;
this.emit('change', 'tabular');
}

getTabular() {
return Promise.resolve(this._tabular ? this._tabular() : null);
}

}

export { DataAdapter };
2 changes: 2 additions & 0 deletions src/ui/public/inspector/adapters/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { DataAdapter } from './data_adapter';
export { RequestAdapter, RequestStatus } from './request_adapter';
Loading

0 comments on commit 52fcd66

Please sign in to comment.