mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* Fix design of debug spy panel * Simplify the debug spy panel * Fix height of spy content * Use scope bindings for spy * Allow modes to be enabled/disabled based on vis * Make showSpyPanel docs for loader more detailed * Only show default panels when using courier * Fix styling of req/resp spies * Fix title styling in spy panel * Get rid of visData in spy panels * Use tabs instead of select box * Ensure uiState in spy * Restyle code in default editor * Remove unnecessary if protection * Use local scope variable for rowsPerPage Due to issues with primitive datatype bindings in Angular this anyway didn't save it correctly back into the variable. * Pull all logic into spy directive * Fix bug when closing panel * Check for minimum chart size * Skip spy tests for now * Fix functional tests * Improve uiState mock * Create unit tests * Remove dead scope binding
This commit is contained in:
parent
60f809826d
commit
008034c1aa
18 changed files with 525 additions and 263 deletions
|
@ -1,35 +1,23 @@
|
|||
<div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
Vis State
|
||||
</label>
|
||||
<pre>{{visStateJson}}</pre>
|
||||
<div class="euiFlexGroup euiFlexGroup--responsive euiFlexGroup--gutterMedium">
|
||||
<div class="euiFlexItem">
|
||||
<h3 class="euiTitle euiTitle--small">
|
||||
Vis State
|
||||
</h3>
|
||||
<div class="euiCodeBlock euiCodeBlock--light euiCodeBlock--fontSmall euiCodeBlock--paddingSmall">
|
||||
<code class="euiCodeBlock__code">
|
||||
<pre class="euiCodeBlock__pre">{{vis.getEnabledState() | json}}</pre>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Details</label>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
Type Name
|
||||
</th>
|
||||
<td>
|
||||
{{vis.type.name}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
Hierarchical Data
|
||||
</th>
|
||||
<td>
|
||||
{{vis.isHierarchical()}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="euiFlexItem">
|
||||
<h3 class="euiTitle euiTitle--small">Details</h3>
|
||||
<dl class="euiDescriptionList euiDescriptionList--column">
|
||||
<dt class="euiDescriptionList__title">Type Name</dt>
|
||||
<dd class="euiDescriptionList__description">{{vis.type.name}}</dd>
|
||||
<dt class="euiDescriptionList__title">Hierarchical Data</dt>
|
||||
<dd class="euiDescriptionList__description">{{vis.isHierarchical()}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,12 +6,7 @@ function VisDetailsSpyProvider() {
|
|||
name: 'debug',
|
||||
display: 'Debug',
|
||||
template: visDebugSpyPanelTemplate,
|
||||
order: 5,
|
||||
link: function ($scope) {
|
||||
$scope.$watch('vis.getEnabledState() | json', function (json) {
|
||||
$scope.visStateJson = json;
|
||||
});
|
||||
}
|
||||
order: 5
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,21 +10,32 @@
|
|||
<i class="fa fa-danger"></i> Request Failed
|
||||
</div>
|
||||
|
||||
<div ng-if="spy.mode.name === 'request'">
|
||||
<label>
|
||||
Elasticsearch request body
|
||||
</label>
|
||||
<pre data-test-subj="visualizationEsRequestBody">{{req.fetchParams.body | json}}</pre>
|
||||
<div ng-if="mode === 'request'">
|
||||
<h3 class="euiTitle euiTitle--small">
|
||||
Elasticsearch request body
|
||||
</h3>
|
||||
<div
|
||||
data-test-subj="visualizationEsRequestBody"
|
||||
class="euiCodeBlock euiCodeBlock--light euiCodeBlock--fontSmall euiCodeBlock--paddingSmall"
|
||||
>
|
||||
<code class="euiCodeBlock__code">
|
||||
<pre class="euiCodeBlock__pre">{{req.fetchParams.body | json}}</pre>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="spy.mode.name === 'response'">
|
||||
<label>
|
||||
Elasticsearch response body
|
||||
</label>
|
||||
<pre>{{req.resp | json}}</pre>
|
||||
<div ng-if="mode === 'response'">
|
||||
<h3 class="euiTitle euiTitle--small">
|
||||
Elasticsearch response body
|
||||
</h3>
|
||||
<div class="euiCodeBlock euiCodeBlock--light euiCodeBlock--fontSmall euiCodeBlock--paddingSmall">
|
||||
<code class="euiCodeBlock__code">
|
||||
<pre class="euiCodeBlock__pre">{{req.resp | json}}</pre>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="spy.mode.name === 'stats'">
|
||||
<div ng-if="mode === 'stats'">
|
||||
<table class="table">
|
||||
<tr ng-repeat="pair in stats">
|
||||
<td>{{pair[0]}}</td>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import reqRespStatsHTML from 'plugins/spy_modes/req_resp_stats_spy_mode.html';
|
||||
import { SpyModesRegistryProvider } from 'ui/registry/spy_modes';
|
||||
|
||||
const linkReqRespStats = function ($scope) {
|
||||
const linkReqRespStats = function (mode, $scope) {
|
||||
$scope.mode = mode;
|
||||
$scope.$bind('req', 'searchSource.history[searchSource.history.length - 1]');
|
||||
$scope.$watchMulti([
|
||||
'req',
|
||||
|
@ -27,6 +28,10 @@ const linkReqRespStats = function ($scope) {
|
|||
});
|
||||
};
|
||||
|
||||
function shouldShowSpyMode(vis) {
|
||||
return vis.type.requestHandler === 'courier' && vis.type.requiresSearch;
|
||||
}
|
||||
|
||||
SpyModesRegistryProvider
|
||||
.register(function () {
|
||||
return {
|
||||
|
@ -34,7 +39,8 @@ SpyModesRegistryProvider
|
|||
display: 'Request',
|
||||
order: 2,
|
||||
template: reqRespStatsHTML,
|
||||
link: linkReqRespStats
|
||||
showMode: shouldShowSpyMode,
|
||||
link: linkReqRespStats.bind(null, 'request')
|
||||
};
|
||||
})
|
||||
.register(function () {
|
||||
|
@ -43,7 +49,8 @@ SpyModesRegistryProvider
|
|||
display: 'Response',
|
||||
order: 3,
|
||||
template: reqRespStatsHTML,
|
||||
link: linkReqRespStats
|
||||
showMode: shouldShowSpyMode,
|
||||
link: linkReqRespStats.bind(null, 'response')
|
||||
};
|
||||
})
|
||||
.register(function () {
|
||||
|
@ -52,6 +59,7 @@ SpyModesRegistryProvider
|
|||
display: 'Statistics',
|
||||
order: 4,
|
||||
template: reqRespStatsHTML,
|
||||
link: linkReqRespStats
|
||||
showMode: shouldShowSpyMode,
|
||||
link: linkReqRespStats.bind(null, 'stats')
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<kbn-agg-table
|
||||
table="table"
|
||||
export-title="vis.title"
|
||||
per-page="spy.params.spyPerPage">
|
||||
per-page="rowsPerPage">
|
||||
</kbn-agg-table>
|
||||
|
|
|
@ -12,17 +12,16 @@ function VisSpyTableProvider(Notifier, $filter, $rootScope, config, Private) {
|
|||
display: 'Table',
|
||||
order: 1,
|
||||
template: tableSpyModeTemplate,
|
||||
showMode: vis => vis.type.requestHandler === 'courier' && vis.type.requiresSearch,
|
||||
link: function tableLinkFn($scope) {
|
||||
$rootScope.$watchMulti.call($scope, [
|
||||
'vis',
|
||||
'visData'
|
||||
'searchSource.rawResponse'
|
||||
], function () {
|
||||
if (!$scope.vis || !$scope.visData) {
|
||||
if (!$scope.vis || !$scope.searchSource.rawResponse) {
|
||||
$scope.table = null;
|
||||
} else {
|
||||
if (!$scope.spy.params.spyPerPage) {
|
||||
$scope.spy.params.spyPerPage = PER_PAGE_DEFAULT;
|
||||
}
|
||||
$scope.rowsPerPage = PER_PAGE_DEFAULT;
|
||||
|
||||
$scope.table = tabifyAggResponse($scope.vis, $scope.searchSource.rawResponse, {
|
||||
canSplit: false,
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
import _ from 'lodash';
|
||||
const keys = {};
|
||||
let values = {};
|
||||
export default {
|
||||
get: function (path, def) {
|
||||
return keys[path] == null ? def : keys[path];
|
||||
return _.get(values, path, def);
|
||||
},
|
||||
set: function (path, val) {
|
||||
keys[path] = val;
|
||||
_.set(values, path, val);
|
||||
return val;
|
||||
},
|
||||
setSilent: function (path, val) {
|
||||
keys[path] = val;
|
||||
_.set(values, path, val);
|
||||
return val;
|
||||
},
|
||||
emit: _.noop,
|
||||
on: _.noop,
|
||||
off: _.noop
|
||||
off: _.noop,
|
||||
_reset: function () {
|
||||
values = {};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,5 +16,11 @@
|
|||
data-title="{{vis.title}}"
|
||||
data-description="{{vis.description}}"
|
||||
>
|
||||
<visualization vis="vis" vis-data="visData" ui-state="uiState" search-source="searchSource" show-spy-panel="showSpyPanel" />
|
||||
<visualization
|
||||
vis="vis"
|
||||
vis-data="visData"
|
||||
ui-state="uiState"
|
||||
search-source="searchSource"
|
||||
show-spy-panel="showSpyPanel"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,49 +1,62 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import ngMock from 'ng_mock';
|
||||
import angular from 'angular';
|
||||
import { VisProvider } from 'ui/vis';
|
||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import FixturesStubbedSearchSourceProvider from 'fixtures/stubbed_search_source';
|
||||
import MockState from 'fixtures/mock_state';
|
||||
import { uiRegistry } from 'ui/registry/_registry';
|
||||
import { SpyModesRegistryProvider } from 'ui/registry/spy_modes';
|
||||
import mockUiState from 'fixtures/mock_ui_state';
|
||||
|
||||
describe('visualize spy directive', function () {
|
||||
let $rootScope;
|
||||
describe('visualize spy panel', function () {
|
||||
let $scope;
|
||||
let $compile;
|
||||
let $timeout;
|
||||
let $el;
|
||||
let visElement;
|
||||
let Vis;
|
||||
let indexPattern;
|
||||
let fixtures;
|
||||
let searchSource;
|
||||
let appState;
|
||||
let vis;
|
||||
let spyModeStubRegistry;
|
||||
|
||||
beforeEach(ngMock.module('kibana', 'kibana/table_vis', (PrivateProvider) => {
|
||||
spyModeStubRegistry = uiRegistry({
|
||||
name: 'spyModes',
|
||||
index: ['name'],
|
||||
order: ['order']
|
||||
});
|
||||
|
||||
PrivateProvider.swap(SpyModesRegistryProvider, spyModeStubRegistry);
|
||||
}));
|
||||
|
||||
beforeEach(ngMock.module('kibana', 'kibana/table_vis'));
|
||||
beforeEach(ngMock.inject(function (Private, $injector) {
|
||||
$rootScope = $injector.get('$rootScope');
|
||||
$scope = $injector.get('$rootScope').$new();
|
||||
$timeout = $injector.get('$timeout');
|
||||
$compile = $injector.get('$compile');
|
||||
visElement = angular.element('<div>');
|
||||
visElement.width(500);
|
||||
visElement.height(500);
|
||||
fixtures = require('fixtures/fake_hierarchical_data');
|
||||
Vis = Private(VisProvider);
|
||||
appState = new MockState({ filters: [] });
|
||||
appState.toJSON = () => { return {}; };
|
||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
searchSource = Private(FixturesStubbedSearchSourceProvider);
|
||||
const requiresSearch = false;
|
||||
vis = new CreateVis(null, requiresSearch);
|
||||
vis = new CreateVis(null, false);
|
||||
init(vis, fixtures.oneRangeBucket);
|
||||
}));
|
||||
|
||||
// basically a parameterized beforeEach
|
||||
function init(vis, esResponse) {
|
||||
vis.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); });
|
||||
$rootScope.spy = {};
|
||||
$rootScope.vis = vis;
|
||||
$rootScope.esResponse = esResponse;
|
||||
$rootScope.uiState = require('fixtures/mock_ui_state');
|
||||
$rootScope.searchSource = searchSource;
|
||||
$el = $('<visualize-spy>');
|
||||
$compile($el)($rootScope);
|
||||
$rootScope.$apply();
|
||||
mockUiState._reset();
|
||||
$scope.vis = vis;
|
||||
$scope.esResponse = esResponse;
|
||||
$scope.uiState = mockUiState;
|
||||
$scope.searchSource = searchSource;
|
||||
$scope.visElement = visElement;
|
||||
}
|
||||
|
||||
function CreateVis(params, requiresSearch) {
|
||||
|
@ -72,36 +85,221 @@ describe('visualize spy directive', function () {
|
|||
return vis;
|
||||
}
|
||||
|
||||
it('toggleDisplay toggles spy display', () => {
|
||||
$rootScope.toggleDisplay();
|
||||
$rootScope.$apply();
|
||||
let mode = _.get($rootScope.spy, 'mode.name');
|
||||
expect(mode).to.equal('table');
|
||||
|
||||
$rootScope.toggleDisplay();
|
||||
$rootScope.$apply();
|
||||
mode = _.get($rootScope.spy, 'mode.name');
|
||||
expect(mode).to.be.undefined;
|
||||
function compile() {
|
||||
const spyElem = $('<visualize-spy vis="vis" vis-element="visElement" search-source="searchSource" ui-state="uiState">');
|
||||
const $el = $compile(spyElem)($scope);
|
||||
$scope.$apply();
|
||||
|
||||
$el.toggleButton = $el.find('[data-test-subj="spyToggleButton"]');
|
||||
$el.maximizedButton = $el.find('[data-test-subj="toggleSpyFullscreen"]');
|
||||
$el.panel = $el.find('[data-test-subj="spyContainer"]');
|
||||
$el.tabs = $el.find('[data-test-subj="spyModTabs"]');
|
||||
|
||||
return $el;
|
||||
}
|
||||
|
||||
function fillRegistryAndCompile() {
|
||||
spyModeStubRegistry.register(() => ({
|
||||
name: 'spymode1',
|
||||
display: 'SpyMode1',
|
||||
order: 1,
|
||||
template: '<div></div>',
|
||||
}));
|
||||
spyModeStubRegistry.register(() => ({
|
||||
name: 'spymode2',
|
||||
display: 'SpyMode2',
|
||||
order: 2,
|
||||
template: '<div></div>',
|
||||
}));
|
||||
|
||||
$el = compile();
|
||||
}
|
||||
|
||||
function openSpy(el = $el) {
|
||||
el.toggleButton.click();
|
||||
}
|
||||
|
||||
// Returns an array of the title of all shown mode tabs.
|
||||
function getModeTabTitles($el) {
|
||||
const tabElems = $el.tabs.find('button').get();
|
||||
return tabElems.map(btn => btn.textContent.trim());
|
||||
}
|
||||
|
||||
describe('toggle button', () => {
|
||||
|
||||
it('should not be shown if no spy mode is registered', () => {
|
||||
const $el = compile();
|
||||
expect($el.toggleButton.length).to.equal(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('toggleFullPage toggles full page display', () => {
|
||||
$rootScope.spy = { mode: { name: 'table' } };
|
||||
$rootScope.toggleFullPage();
|
||||
$rootScope.$apply();
|
||||
let mode = _.get($rootScope.spy, 'mode.fill');
|
||||
expect(mode).to.equal(true);
|
||||
describe('open and closing the spy panel', () => {
|
||||
|
||||
$rootScope.toggleFullPage();
|
||||
$rootScope.$apply();
|
||||
mode = _.get($rootScope.spy, 'mode.fill');
|
||||
expect(mode).to.equal(false);
|
||||
beforeEach(fillRegistryAndCompile);
|
||||
|
||||
it('should show spy-panel on toggle click', () => {
|
||||
expect($el.panel.hasClass('ng-hide')).to.equal(true);
|
||||
$el.toggleButton.click();
|
||||
expect($el.panel.hasClass('ng-hide')).to.equal(false);
|
||||
});
|
||||
|
||||
it('should hide spy-panel on toggle button, when opened', () => {
|
||||
$el.toggleButton.click();
|
||||
expect($el.panel.hasClass('ng-hide')).to.equal(false);
|
||||
$el.toggleButton.click();
|
||||
expect($el.panel.hasClass('ng-hide')).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('onSpyModeChange updates the spy display mode', () => {
|
||||
$rootScope.selectedModeName = 'table';
|
||||
$rootScope.onSpyModeChange();
|
||||
$rootScope.$apply();
|
||||
const mode = _.get($rootScope.spy, 'mode.name');
|
||||
expect(mode).to.equal('table');
|
||||
describe('maximized mode', () => {
|
||||
|
||||
beforeEach(fillRegistryAndCompile);
|
||||
|
||||
it('should toggle to maximized mode when maximized button is clicked', () => {
|
||||
openSpy();
|
||||
$el.maximizedButton.click();
|
||||
expect($el.panel.hasClass('only')).to.equal(true);
|
||||
expect(visElement.hasClass('spy-only')).to.equal(true);
|
||||
});
|
||||
|
||||
it('should exit maximized mode on a second click', () => {
|
||||
openSpy();
|
||||
$el.maximizedButton.click();
|
||||
expect($el.panel.hasClass('only')).to.equal(true);
|
||||
expect(visElement.hasClass('spy-only')).to.equal(true);
|
||||
$el.maximizedButton.click();
|
||||
expect($el.panel.hasClass('only')).to.equal(false);
|
||||
expect(visElement.hasClass('spy-only')).to.equal(false);
|
||||
});
|
||||
|
||||
it('will be forced when vis would be too small otherwise', () => {
|
||||
visElement.height(50);
|
||||
openSpy();
|
||||
$timeout.flush();
|
||||
expect($el.panel.hasClass('only')).to.equal(true);
|
||||
expect(visElement.hasClass('spy-only')).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('spy modes', () => {
|
||||
|
||||
function registerRegularPanels() {
|
||||
spyModeStubRegistry.register(() => ({
|
||||
name: 'spymode2',
|
||||
display: 'SpyMode2',
|
||||
order: 2,
|
||||
template: '<div class="spymode2"></div>',
|
||||
}));
|
||||
spyModeStubRegistry.register(() => ({
|
||||
name: 'spymode1',
|
||||
display: 'SpyMode1',
|
||||
order: 1,
|
||||
template: '<div class="spymode1"></div>',
|
||||
}));
|
||||
}
|
||||
|
||||
it('should show registered spy modes as tabs', () => {
|
||||
registerRegularPanels();
|
||||
const $el = compile();
|
||||
openSpy($el);
|
||||
expect($el.tabs.find('button').length).to.equal(2);
|
||||
expect(getModeTabTitles($el)).to.eql(['SpyMode1', 'SpyMode2']);
|
||||
});
|
||||
|
||||
it('should by default be on the first spy mode when opening', async () => {
|
||||
registerRegularPanels();
|
||||
const $el = compile();
|
||||
openSpy($el);
|
||||
expect($el.panel.find('.spymode1').length).to.equal(1);
|
||||
});
|
||||
|
||||
describe('conditional spy modes', () => {
|
||||
|
||||
let filterOutSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
filterOutSpy = sinon.spy(() => false);
|
||||
spyModeStubRegistry.register(() => ({
|
||||
name: 'test',
|
||||
display: 'ShouldBeFiltered',
|
||||
showMode: filterOutSpy,
|
||||
order: 1,
|
||||
template: '<div></div>'
|
||||
}));
|
||||
spyModeStubRegistry.register(() => ({
|
||||
name: 'test2',
|
||||
display: 'ShouldNotBeFiltered',
|
||||
order: 2,
|
||||
template: '<div></div>'
|
||||
}));
|
||||
spyModeStubRegistry.register(() => ({
|
||||
name: 'test3',
|
||||
display: 'Test3',
|
||||
order: 3,
|
||||
showMode: () => true,
|
||||
template: '<div></div>'
|
||||
}));
|
||||
|
||||
$el = compile();
|
||||
openSpy();
|
||||
});
|
||||
|
||||
it('should filter out panels, that return false in showMode', () => {
|
||||
expect(getModeTabTitles($el)).not.to.include('ShouldBeFiltered');
|
||||
});
|
||||
|
||||
it('should show modes without a showMode function', () => {
|
||||
expect(getModeTabTitles($el)).to.include('ShouldNotBeFiltered');
|
||||
});
|
||||
|
||||
it('should show mods whose showMode returns true', () => {
|
||||
expect(getModeTabTitles($el)).to.include('Test3');
|
||||
});
|
||||
|
||||
it('should pass the visualization to the showMode method', () => {
|
||||
expect(filterOutSpy.called).to.equal(true);
|
||||
expect(filterOutSpy.getCall(0).args[0]).to.equal(vis);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('uiState', () => {
|
||||
beforeEach(fillRegistryAndCompile);
|
||||
|
||||
it('should sync the active tab to the uiState', () => {
|
||||
expect($scope.uiState.get('spy.mode.name', null)).to.be.null;
|
||||
openSpy();
|
||||
expect($scope.uiState.get('spy.mode.name', null)).to.equal('spymode1');
|
||||
});
|
||||
|
||||
it('should sync uiState when closing the panel', () => {
|
||||
openSpy();
|
||||
expect($scope.uiState.get('spy.mode.name', null)).to.equal('spymode1');
|
||||
$el.toggleButton.click();
|
||||
expect($scope.uiState.get('spy.mode.name', null)).to.equal(null);
|
||||
});
|
||||
|
||||
it('should sync uiState when maximizing', () => {
|
||||
openSpy();
|
||||
expect($scope.uiState.get('spy.mode.fill', null)).to.equal(null);
|
||||
$el.maximizedButton.click(); // Maximize it initially
|
||||
expect($scope.uiState.get('spy.mode.fill', false)).to.equal(true);
|
||||
$el.maximizedButton.click(); // Reset maximized state again
|
||||
expect($scope.uiState.get('spy.mode.fill', false)).to.equal(false);
|
||||
});
|
||||
|
||||
it('should also reset fullscreen when closing panel', () => {
|
||||
openSpy();
|
||||
$el.maximizedButton.click();
|
||||
expect($scope.uiState.get('spy.mode.fill', false)).to.equal(true);
|
||||
$el.toggleButton.click(); // Close spy panel
|
||||
expect($scope.uiState.get('spy.mode.fill', null)).to.equal(null);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -24,7 +24,9 @@ import { EmbeddedVisualizeHandler } from './embedded_visualize_handler';
|
|||
* either a date in ISO format, or a valid datetime Elasticsearch expression,
|
||||
* e.g.: { min: 'now-7d/d', max: 'now' }
|
||||
* @property {boolean} showSpyPanel Whether or not the spy panel should be available
|
||||
* on this chart. (default: false)
|
||||
* on this chart. If set to true, spy panels will only be shown if there are
|
||||
* spy panels available for this specific visualization, since not every visualization
|
||||
* supports all spy panels. (default: false)
|
||||
* @property {boolean} append If set to true, the visualization will be appended
|
||||
* to the passed element instead of replacing all its content. (default: false)
|
||||
* @property {string} cssClass If specified this CSS class (or classes with space separated)
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<div class="visualize-show-spy">
|
||||
<div
|
||||
class="visualize-show-spy"
|
||||
ng-if="modes.length > 0"
|
||||
>
|
||||
<button
|
||||
data-test-subj="spyToggleButton"
|
||||
ng-click="toggleDisplay()"
|
||||
|
@ -7,45 +10,51 @@
|
|||
>
|
||||
<span
|
||||
class="kuiIcon"
|
||||
ng-class="spy.mode.name ? 'fa-chevron-circle-down' : 'fa-chevron-circle-up'"
|
||||
ng-class="shouldShowSpyPanel() ? 'fa-chevron-circle-down' : 'fa-chevron-circle-up'"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-test-subj="spyContainer"
|
||||
class="visualize-spy-container"
|
||||
ng-show="spy.mode.name"
|
||||
ng-class="{ 'only': maximizedSpy || forceMaximized }"
|
||||
ng-show="shouldShowSpyPanel()"
|
||||
>
|
||||
<div class="kuiBar kuiVerticalRhythm">
|
||||
<div class="kuiBarSection">
|
||||
<select
|
||||
class="kuiSelect visualize-fixed-position"
|
||||
ng-model="selectedModeName"
|
||||
ng-change="onSpyModeChange()"
|
||||
data-test-subj="spyModeSelect"
|
||||
<div class="visualize-spy__tab-container euiTabs euiTabs--small">
|
||||
<div role="tablist" class="visualize-spy__tabs" data-test-subj="spyModTabs">
|
||||
<button
|
||||
role="tab"
|
||||
class="euiTab"
|
||||
ng-repeat="mode in modes"
|
||||
aria-selected="{{mode.name === currentMode}}"
|
||||
ng-class="{'euiTab-isSelected': mode.name === currentMode}"
|
||||
data-test-subj="spyModeSelect-{{::mode.name}}"
|
||||
ng-click="setSpyMode(mode.name)"
|
||||
>
|
||||
<option
|
||||
ng-repeat="mode in modes.inOrder"
|
||||
value="{{mode.name}}"
|
||||
data-test-subj="spyModeSelect-{{ mode.name }}"
|
||||
>
|
||||
{{mode.display}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="kuiBarSection">
|
||||
<button class="kuiButton kuiButton--hollow" ng-click="toggleFullPage()">
|
||||
<span
|
||||
class="kuiIcon"
|
||||
ng-class="spy.mode.fill ? 'fa-compress' : 'fa-expand'"
|
||||
></span>
|
||||
<span class="euiTab__content">
|
||||
{{::mode.display}}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
data-test-subj="toggleSpyFullscreen"
|
||||
aria-label="Toggle spy panel fullscreen"
|
||||
class="kuiButton kuiButton--hollow"
|
||||
ng-hide="forceMaximized"
|
||||
ng-click="toggleMaximize()"
|
||||
>
|
||||
<span
|
||||
class="kuiIcon"
|
||||
ng-class="currentMode.fill ? 'fa-compress' : 'fa-expand'"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-test-subj="spyContentContainer"
|
||||
data-spy-content-container
|
||||
class="kuiVerticalRhythm"
|
||||
class="kuiVerticalRhythm visualize-spy__content-container"
|
||||
></div>
|
||||
</div>
|
||||
|
|
|
@ -1,106 +1,181 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import { SpyModesRegistryProvider } from 'ui/registry/spy_modes';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import spyTemplate from 'ui/visualize/spy.html';
|
||||
import { PersistedState } from 'ui/persisted_state';
|
||||
|
||||
uiModules
|
||||
.get('app/visualize')
|
||||
.directive('visualizeSpy', function (Private, $compile) {
|
||||
.directive('visualizeSpy', function (Private, $compile, $timeout) {
|
||||
|
||||
const spyModes = Private(SpyModesRegistryProvider);
|
||||
const defaultMode = spyModes.inOrder[0].name;
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: spyTemplate,
|
||||
scope: {
|
||||
vis: '<',
|
||||
searchSource: '<',
|
||||
uiState: '<',
|
||||
visElement: '<',
|
||||
},
|
||||
link: function ($scope, $el) {
|
||||
|
||||
// If no uiState has been passed, create a local one for this spy.
|
||||
if (!$scope.uiState) $scope.uiState = new PersistedState({});
|
||||
|
||||
let currentSpy;
|
||||
let defaultModeName;
|
||||
|
||||
const $container = $el.find('[data-spy-content-container]');
|
||||
let fullPageSpy = _.get($scope.spy, 'mode.fill', false);
|
||||
$scope.modes = spyModes;
|
||||
$scope.spy.params = $scope.spy.params || {};
|
||||
|
||||
function getSpyObject(name) {
|
||||
name = _.isUndefined(name) ? $scope.spy.mode.name : name;
|
||||
fullPageSpy = (_.isNull(name)) ? false : fullPageSpy;
|
||||
$scope.modes = [];
|
||||
|
||||
return {
|
||||
name: name,
|
||||
fill: fullPageSpy,
|
||||
};
|
||||
$scope.currentMode = null;
|
||||
$scope.maximizedSpy = false;
|
||||
$scope.forceMaximized = false;
|
||||
|
||||
function checkForcedMaximized() {
|
||||
$timeout(() => {
|
||||
if ($scope.visElement && $scope.visElement.height() < 180) {
|
||||
$scope.forceMaximized = true;
|
||||
} else {
|
||||
$scope.forceMaximized = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setSpyMode(modeName) {
|
||||
if (!_.isString(modeName)) modeName = null;
|
||||
$scope.spy.mode = getSpyObject(modeName);
|
||||
$scope.$emit('render');
|
||||
checkForcedMaximized();
|
||||
|
||||
|
||||
/**
|
||||
* Filter for modes that should actually be active for this visualization.
|
||||
* This will call the showMode method of the mode, pass it the vis object.
|
||||
* Depending on whether or not that returns a truthy value, it will be shown
|
||||
* or not. If the method is not present, the mode will always be shown.
|
||||
*/
|
||||
function filterModes() {
|
||||
$scope.modes = spyModes.inOrder.filter(mode =>
|
||||
mode.showMode ? mode.showMode($scope.vis) : true
|
||||
);
|
||||
defaultModeName = $scope.modes.length > 0 ? $scope.modes[0].name : null;
|
||||
}
|
||||
|
||||
const renderSpy = function (spyName) {
|
||||
const newMode = $scope.modes.byName[spyName];
|
||||
filterModes();
|
||||
$scope.$watch('vis', filterModes);
|
||||
|
||||
// clear the current value
|
||||
if (currentSpy) {
|
||||
currentSpy.$container && currentSpy.$container.remove();
|
||||
currentSpy.$scope && currentSpy.$scope.$destroy();
|
||||
$scope.spy.mode = {};
|
||||
currentSpy = null;
|
||||
function syncFromUiState() {
|
||||
$scope.currentMode = $scope.uiState.get('spy.mode.name');
|
||||
$scope.maximizedSpy = $scope.uiState.get('spy.mode.fill');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write our current state into the uiState.
|
||||
* This will write the name and fill (maximized) into the uiState
|
||||
* if a panel is opened (currentMode is set) or it will otherwise
|
||||
* remove the spy key from the uiState.
|
||||
*/
|
||||
function updateUiState() {
|
||||
if ($scope.currentMode) {
|
||||
$scope.uiState.set('spy.mode', {
|
||||
name: $scope.currentMode,
|
||||
fill: $scope.maximizedSpy,
|
||||
});
|
||||
} else {
|
||||
$scope.uiState.set('spy', null);
|
||||
}
|
||||
}
|
||||
|
||||
// no further changes
|
||||
if (!newMode) return;
|
||||
// Initially sync the panel state from the uiState.
|
||||
syncFromUiState();
|
||||
|
||||
// update the spy mode and append to the container
|
||||
const selectedSpyMode = getSpyObject(newMode.name);
|
||||
$scope.spy.mode = selectedSpyMode;
|
||||
$scope.selectedModeName = selectedSpyMode.name;
|
||||
// Whenever the uiState changes, update the settings from it.
|
||||
$scope.uiState.on('change', syncFromUiState);
|
||||
$scope.$on('$destroy', () => $scope.uiState.off('change', syncFromUiState));
|
||||
|
||||
currentSpy = _.assign({
|
||||
$scope: $scope.$new(),
|
||||
$container: $('<div class="visualize-spy-content">').appendTo($container)
|
||||
}, $scope.spy.mode);
|
||||
|
||||
currentSpy.$container.append($compile(newMode.template)(currentSpy.$scope));
|
||||
newMode.link && newMode.link(currentSpy.$scope, currentSpy.$container);
|
||||
$scope.setSpyMode = function setSpyMode(modeName) {
|
||||
$scope.currentMode = modeName;
|
||||
updateUiState();
|
||||
$scope.$emit('render');
|
||||
};
|
||||
|
||||
$scope.toggleDisplay = function () {
|
||||
const modeName = _.get($scope.spy, 'mode.name');
|
||||
setSpyMode(modeName ? null : defaultMode);
|
||||
// If the spy panel is already shown (a currentMode is set),
|
||||
// close the panel by setting the name to null, otherwise open the
|
||||
// panel (i.e. set it to the default mode name).
|
||||
if ($scope.currentMode) {
|
||||
$scope.setSpyMode(null);
|
||||
$scope.forceMaximized = false;
|
||||
} else {
|
||||
$scope.setSpyMode(defaultModeName);
|
||||
checkForcedMaximized();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleFullPage = function () {
|
||||
fullPageSpy = !fullPageSpy;
|
||||
$scope.spy.mode = getSpyObject();
|
||||
/**
|
||||
* Should we currently show the spy panel. True if a currentMode has been set.
|
||||
*/
|
||||
$scope.shouldShowSpyPanel = () => {
|
||||
return !!$scope.currentMode;
|
||||
};
|
||||
|
||||
$scope.onSpyModeChange = function onSpyModeChange() {
|
||||
setSpyMode($scope.selectedModeName);
|
||||
/**
|
||||
* Toggle maximized state of spy panel and update the UI state.
|
||||
*/
|
||||
$scope.toggleMaximize = function () {
|
||||
$scope.maximizedSpy = !$scope.maximizedSpy;
|
||||
updateUiState();
|
||||
};
|
||||
|
||||
if ($scope.uiState) {
|
||||
// sync external uiState changes
|
||||
const syncUIState = () => $scope.spy.mode = $scope.uiState.get('spy.mode');
|
||||
$scope.uiState.on('change', syncUIState);
|
||||
$scope.$on('$destroy', () => $scope.uiState.off('change', syncUIState));
|
||||
}
|
||||
/**
|
||||
* Whenever the maximized state changes, we also need to toggle the class
|
||||
* of the visualization.
|
||||
*/
|
||||
$scope.$watchMulti(['maximizedSpy', 'forceMaximized'], () => {
|
||||
$scope.visElement.toggleClass('spy-only', $scope.maximizedSpy || $scope.forceMaximized);
|
||||
});
|
||||
|
||||
// re-render the spy when the name of fill modes change
|
||||
$scope.$watchMulti([
|
||||
'spy.mode.name',
|
||||
'spy.mode.fill'
|
||||
], function (newVals, oldVals) {
|
||||
// update the ui state, but only if it really changes
|
||||
const changedVals = newVals.filter((val) => !_.isUndefined(val)).length > 0;
|
||||
if (changedVals && !_.isEqual(newVals, oldVals)) {
|
||||
if ($scope.uiState) $scope.uiState.set('spy.mode', $scope.spy.mode);
|
||||
/**
|
||||
* Watch for changes of the currentMode. Whenever it changes, we render
|
||||
* the new mode into the template. Therefore we remove the previously rendered
|
||||
* mode (if existing) and compile and bind the template of the new mode.
|
||||
*/
|
||||
$scope.$watch('currentMode', (mode, prevMode) => {
|
||||
if (mode === prevMode && (currentSpy && currentSpy.mode === mode)) {
|
||||
// When the mode hasn't changed and we have already rendered it, return.
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure the fill mode is synced
|
||||
fullPageSpy = _.get($scope.spy, 'mode.fill', fullPageSpy);
|
||||
const newMode = spyModes.byName[mode];
|
||||
|
||||
renderSpy(_.get($scope.spy, 'mode.name', null));
|
||||
if (currentSpy) {
|
||||
// If we already have a spy loaded, remove that HTML element and
|
||||
// destroy the previous Angular scope.
|
||||
currentSpy.$container.remove();
|
||||
currentSpy.$scope.$destroy();
|
||||
currentSpy = null;
|
||||
}
|
||||
|
||||
// If we want haven't specified a new mode we won't do anything further.
|
||||
if (!newMode) {
|
||||
// Reset the forced maximized flag if we are about to close the panel.
|
||||
$scope.forceMaximized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const contentScope = $scope.$new();
|
||||
const contentContainer = $('<div class="visualize-spy-content">');
|
||||
contentContainer.append($compile(newMode.template)(contentScope));
|
||||
|
||||
$container.append(contentContainer);
|
||||
|
||||
currentSpy = {
|
||||
$scope: contentScope,
|
||||
$container: contentContainer,
|
||||
mode: mode,
|
||||
};
|
||||
|
||||
newMode.link && newMode.link(currentSpy.$scope, currentSpy.$element);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,4 +21,10 @@
|
|||
class="visualize-chart"></div>
|
||||
<visualize-legend ng-if="addLegend"></visualize-legend>
|
||||
</div>
|
||||
<visualize-spy ng-if="showSpyPanel"></visualize-spy>
|
||||
<visualize-spy
|
||||
ng-if="showSpyPanel"
|
||||
vis="vis"
|
||||
vis-element="visElement"
|
||||
search-source="searchSource"
|
||||
ui-state="uiState"
|
||||
></visualize-spy>
|
||||
|
|
|
@ -26,13 +26,11 @@ uiModules
|
|||
},
|
||||
template: visualizationTemplate,
|
||||
link: function ($scope, $el) {
|
||||
const minVisChartHeight = 180;
|
||||
const resizeChecker = new ResizeChecker($el);
|
||||
|
||||
//todo: lets make this a simple function call.
|
||||
const getVisEl = jQueryGetter('.visualize-chart');
|
||||
const getVisContainer = jQueryGetter('.vis-container');
|
||||
const getSpyContainer = jQueryGetter('.visualize-spy-container');
|
||||
|
||||
$scope.addLegend = false;
|
||||
|
||||
|
@ -66,26 +64,7 @@ uiModules
|
|||
return legendPositionToVisContainerClassMap[$scope.vis.params.legendPosition];
|
||||
};
|
||||
|
||||
$scope.spy = {};
|
||||
$scope.spy.mode = ($scope.uiState) ? $scope.uiState.get('spy.mode', {}) : {};
|
||||
|
||||
const applyClassNames = function () {
|
||||
const $visEl = getVisContainer();
|
||||
const $spyEl = getSpyContainer();
|
||||
if (!$spyEl) return;
|
||||
|
||||
const fullSpy = ($scope.spy.mode && ($scope.spy.mode.fill || $scope.fullScreenSpy));
|
||||
|
||||
$visEl.toggleClass('spy-only', Boolean(fullSpy));
|
||||
$spyEl.toggleClass('only', Boolean(fullSpy));
|
||||
|
||||
$timeout(function () {
|
||||
if (shouldHaveFullSpy()) {
|
||||
$visEl.addClass('spy-only');
|
||||
$spyEl.addClass('only');
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
$scope.visElement = getVisContainer();
|
||||
|
||||
const loadingDelay = config.get('visualization:loadingDelay');
|
||||
$scope.loadingStyle = {
|
||||
|
@ -93,23 +72,6 @@ uiModules
|
|||
'transition-delay': loadingDelay
|
||||
};
|
||||
|
||||
function shouldHaveFullSpy() {
|
||||
const $visEl = getVisEl();
|
||||
if (!$visEl) return;
|
||||
|
||||
return ($visEl.height() < minVisChartHeight)
|
||||
&& _.get($scope.spy, 'mode.fill')
|
||||
&& _.get($scope.spy, 'mode.name');
|
||||
}
|
||||
|
||||
// spy watchers
|
||||
$scope.$watch('fullScreenSpy', applyClassNames);
|
||||
|
||||
$scope.$watchCollection('spy.mode', function () {
|
||||
$scope.fullScreenSpy = shouldHaveFullSpy();
|
||||
applyClassNames();
|
||||
});
|
||||
|
||||
const Visualization = $scope.vis.type.visualization;
|
||||
const visualization = new Visualization(getVisEl()[0], $scope.vis);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
ui-state="uiState"
|
||||
class="vis-editor-content"
|
||||
search-source="savedObj.searchSource"
|
||||
show-spy-panel="shouldShowSpyPanel()"
|
||||
show-spy-panel="showSpyPanel"
|
||||
/>
|
||||
<visualization
|
||||
ng-if="editorMode!==true"
|
||||
|
@ -15,5 +15,5 @@
|
|||
vis-data="visData"
|
||||
ui-state="uiState"
|
||||
search-source="savedObj.searchSource"
|
||||
show-spy-panel="shouldShowSpyPanel()"
|
||||
show-spy-panel="showSpyPanel"
|
||||
/>
|
||||
|
|
|
@ -77,12 +77,6 @@ uiModules
|
|||
$scope.editorMode = $scope.editorMode || false;
|
||||
$scope.vis.editorMode = $scope.editorMode;
|
||||
|
||||
// spy panel is supported only with courier request handler
|
||||
$scope.shouldShowSpyPanel = () => {
|
||||
if ($scope.vis.type.requestHandler !== 'courier') return false;
|
||||
return $scope.vis.type.requiresSearch && $scope.showSpyPanel;
|
||||
};
|
||||
|
||||
const requestHandler = getHandler(requestHandlers, $scope.vis.type.requestHandler);
|
||||
const responseHandler = getHandler(responseHandlers, $scope.vis.type.responseHandler);
|
||||
|
||||
|
|
|
@ -153,11 +153,6 @@ visualize-spy {
|
|||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.visualize-fixed-position {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.visualize-spy-content {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -187,9 +182,22 @@ visualize-spy {
|
|||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.visualize-show-spy-tab {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
left: 5px;
|
||||
bottom: 0px;
|
||||
}
|
||||
.visualize-show-spy-tab {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
left: 5px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.visualize-spy__content-container {
|
||||
overflow: auto;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.visualize-spy__tab-container {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.visualize-spy__tabs {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
|
|
@ -242,22 +242,22 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
|
|||
|
||||
async openSpyPanel() {
|
||||
log.debug('openSpyPanel');
|
||||
const isOpen = await testSubjects.exists('spyModeSelect');
|
||||
const isOpen = await testSubjects.exists('spyContentContainer');
|
||||
if (!isOpen) {
|
||||
await retry.try(async () => {
|
||||
await this.toggleSpyPanel();
|
||||
await testSubjects.find('spyModeSelect');
|
||||
await testSubjects.find('spyContentContainer');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async closeSpyPanel() {
|
||||
log.debug('closeSpyPanel');
|
||||
let isOpen = await testSubjects.exists('spyModeSelect');
|
||||
let isOpen = await testSubjects.exists('spyContentContainer');
|
||||
if (isOpen) {
|
||||
await retry.try(async () => {
|
||||
await this.toggleSpyPanel();
|
||||
isOpen = await testSubjects.exists('spyModeSelect');
|
||||
isOpen = await testSubjects.exists('spyContentContainer');
|
||||
if (isOpen) {
|
||||
throw new Error('Failed to close spy panel');
|
||||
}
|
||||
|
@ -613,7 +613,6 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
|
|||
}
|
||||
|
||||
async selectTableInSpyPaneSelect() {
|
||||
await testSubjects.click('spyModeSelect');
|
||||
await testSubjects.click('spyModeSelect-table');
|
||||
}
|
||||
|
||||
|
@ -660,7 +659,6 @@ export function VisualizePageProvider({ getService, getPageObjects }) {
|
|||
async getVisualizationRequest() {
|
||||
log.debug('getVisualizationRequest');
|
||||
await this.openSpyPanel();
|
||||
await testSubjects.click('spyModeSelect');
|
||||
await testSubjects.click('spyModeSelect-request');
|
||||
return await testSubjects.getVisibleText('visualizationEsRequestBody');
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue