mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Merge branch 'master' into feature/ingest
This commit is contained in:
commit
0a956057f6
83 changed files with 1565 additions and 492 deletions
|
@ -27,7 +27,7 @@ authority for your Elasticsearch instance.
|
|||
to `false`.
|
||||
`elasticsearch.pingTimeout:`:: *Default: the value of the `elasticsearch.requestTimeout` setting* Time in milliseconds to
|
||||
wait for Elasticsearch to respond to pings.
|
||||
`elasticsearch.requestTimeout:`:: *Default: 300000* Time in milliseconds to wait for responses from the back end or
|
||||
`elasticsearch.requestTimeout:`:: *Default: 30000* Time in milliseconds to wait for responses from the back end or
|
||||
Elasticsearch. This value must be a positive integer.
|
||||
`elasticsearch.requestHeadersWhitelist:`:: *Default: `[ 'authorization' ]`* List of Kibana client-side headers to send to Elasticsearch.
|
||||
To send *no* client-side headers, set this value to [] (an empty list).
|
||||
|
|
|
@ -98,8 +98,10 @@
|
|||
"css-loader": "0.17.0",
|
||||
"csv-parse": "1.1.0",
|
||||
"d3": "3.5.6",
|
||||
"dragula": "3.7.0",
|
||||
"elasticsearch": "10.1.2",
|
||||
"elasticsearch-browser": "10.1.2",
|
||||
"even-better": "7.0.2",
|
||||
"expiry-js": "0.1.7",
|
||||
"exports-loader": "0.6.2",
|
||||
"expose-loader": "0.7.0",
|
||||
|
@ -107,7 +109,6 @@
|
|||
"file-loader": "0.8.4",
|
||||
"font-awesome": "4.4.0",
|
||||
"glob-all": "3.0.1",
|
||||
"good": "6.3.0",
|
||||
"good-squeeze": "2.1.0",
|
||||
"gridster": "0.5.6",
|
||||
"hapi": "8.8.1",
|
||||
|
@ -156,8 +157,10 @@
|
|||
"auto-release-sinon": "1.0.3",
|
||||
"babel-eslint": "4.1.8",
|
||||
"chokidar": "1.4.3",
|
||||
"elasticdump": "2.1.1",
|
||||
"eslint": "1.10.3",
|
||||
"eslint-plugin-mocha": "1.1.0",
|
||||
"event-stream": "3.3.2",
|
||||
"expect.js": "0.3.1",
|
||||
"faker": "1.1.0",
|
||||
"grunt": "0.4.5",
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
server:
|
||||
port: 8274
|
||||
logging:
|
||||
json: true
|
||||
optimize:
|
||||
enabled: false
|
88
src/cli/serve/__tests__/reload_logging_config.js
Normal file
88
src/cli/serve/__tests__/reload_logging_config.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { spawn } from 'child_process';
|
||||
import { writeFileSync, readFile } from 'fs';
|
||||
import { relative, resolve } from 'path';
|
||||
import { safeDump } from 'js-yaml';
|
||||
import es from 'event-stream';
|
||||
import readYamlConfig from '../read_yaml_config';
|
||||
import expect from 'expect.js';
|
||||
const testConfigFile = follow(`fixtures/reload_logging_config/kibana.test.yml`);
|
||||
const cli = follow(`../../../../bin/kibana`);
|
||||
|
||||
function follow(file) {
|
||||
return relative(process.cwd(), resolve(__dirname, file));
|
||||
}
|
||||
|
||||
function setLoggingJson(enabled) {
|
||||
const conf = readYamlConfig(testConfigFile);
|
||||
conf.logging = conf.logging || {};
|
||||
conf.logging.json = enabled;
|
||||
const yaml = safeDump(conf);
|
||||
writeFileSync(testConfigFile, yaml);
|
||||
return conf;
|
||||
}
|
||||
|
||||
describe(`Server logging configuration`, function () {
|
||||
it(`should be reloadable via SIGHUP process signaling`, function (done) {
|
||||
let asserted = false;
|
||||
let json = Infinity;
|
||||
const conf = setLoggingJson(true);
|
||||
const child = spawn(cli, [`--config`, testConfigFile]);
|
||||
|
||||
child.on('error', err => {
|
||||
done(new Error(`error in child process while attempting to reload config.
|
||||
${err.stack || err.message || err}`));
|
||||
});
|
||||
|
||||
child.on('exit', code => {
|
||||
expect(asserted).to.eql(true);
|
||||
expect(code === null || code === 0).to.eql(true);
|
||||
done();
|
||||
});
|
||||
|
||||
child.stdout
|
||||
.pipe(es.split())
|
||||
.pipe(es.mapSync(function (line) {
|
||||
if (!line) {
|
||||
return line; // ignore empty lines
|
||||
}
|
||||
if (json--) {
|
||||
expect(parseJsonLogLine).withArgs(line).to.not.throwError();
|
||||
} else {
|
||||
expectPlainTextLogLine(line);
|
||||
}
|
||||
}));
|
||||
|
||||
function parseJsonLogLine(line) {
|
||||
try {
|
||||
const data = JSON.parse(line);
|
||||
const listening = data.tags.indexOf(`listening`) !== -1;
|
||||
if (listening) {
|
||||
switchToPlainTextLog();
|
||||
}
|
||||
} catch (err) {
|
||||
expect(`Error parsing log line as JSON\n
|
||||
${err.stack || err.message || err}`).to.eql(true);
|
||||
}
|
||||
}
|
||||
|
||||
function switchToPlainTextLog() {
|
||||
json = 2; // ignore both "reloading" messages
|
||||
setLoggingJson(false);
|
||||
child.kill(`SIGHUP`); // reload logging config
|
||||
}
|
||||
|
||||
function expectPlainTextLogLine(line) {
|
||||
// assert
|
||||
const tags = `[\u001b[32minfo\u001b[39m][\u001b[36mconfig\u001b[39m]`;
|
||||
const status = `Reloaded logging configuration due to SIGHUP.`;
|
||||
const expected = `${tags} ${status}`;
|
||||
const actual = line.slice(-expected.length);
|
||||
expect(actual).to.eql(expected);
|
||||
|
||||
// cleanup
|
||||
asserted = true;
|
||||
setLoggingJson(true);
|
||||
child.kill();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -2,11 +2,8 @@ import _ from 'lodash';
|
|||
import { statSync } from 'fs';
|
||||
import { isWorker } from 'cluster';
|
||||
import { resolve } from 'path';
|
||||
|
||||
import readYamlConfig from './read_yaml_config';
|
||||
import { fromRoot } from '../../utils';
|
||||
|
||||
const cwd = process.cwd();
|
||||
import readYamlConfig from './read_yaml_config';
|
||||
|
||||
let canCluster;
|
||||
try {
|
||||
|
@ -28,7 +25,7 @@ const configPathCollector = pathCollector();
|
|||
const pluginDirCollector = pathCollector();
|
||||
const pluginPathCollector = pathCollector();
|
||||
|
||||
function initServerSettings(opts, extraCliOptions) {
|
||||
function readServerSettings(opts, extraCliOptions) {
|
||||
const settings = readYamlConfig(opts.config);
|
||||
const set = _.partial(_.set, settings);
|
||||
const get = _.partial(_.get, settings);
|
||||
|
@ -128,7 +125,8 @@ module.exports = function (program) {
|
|||
}
|
||||
}
|
||||
|
||||
const settings = initServerSettings(opts, this.getUnknownOptions());
|
||||
const getCurrentSettings = () => readServerSettings(opts, this.getUnknownOptions());
|
||||
const settings = getCurrentSettings();
|
||||
|
||||
if (canCluster && opts.dev && !isWorker) {
|
||||
// stop processing the action and handoff to cluster manager
|
||||
|
@ -156,6 +154,13 @@ module.exports = function (program) {
|
|||
process.exit(1); // eslint-disable-line no-process-exit
|
||||
}
|
||||
|
||||
process.on('SIGHUP', function reloadConfig() {
|
||||
const settings = getCurrentSettings();
|
||||
kbnServer.server.log(['info', 'config'], 'Reloading logging configuration due to SIGHUP.');
|
||||
kbnServer.applyLoggingConfiguration(settings);
|
||||
kbnServer.server.log(['info', 'config'], 'Reloaded logging configuration due to SIGHUP.');
|
||||
});
|
||||
|
||||
return kbnServer;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,7 +5,14 @@ import mkdirp from 'mkdirp';
|
|||
import Logger from '../../lib/logger';
|
||||
import list from '../list';
|
||||
import { join } from 'path';
|
||||
import { writeFileSync } from 'fs';
|
||||
import { writeFileSync, appendFileSync } from 'fs';
|
||||
|
||||
|
||||
function createPlugin(name, version, pluginBaseDir) {
|
||||
const pluginDir = join(pluginBaseDir, name);
|
||||
mkdirp.sync(pluginDir);
|
||||
appendFileSync(join(pluginDir, 'package.json'), '{"version": "' + version + '"}');
|
||||
}
|
||||
|
||||
describe('kibana cli', function () {
|
||||
|
||||
|
@ -33,41 +40,61 @@ describe('kibana cli', function () {
|
|||
});
|
||||
|
||||
it('list all of the folders in the plugin folder', function () {
|
||||
mkdirp.sync(join(pluginDir, 'plugin1'));
|
||||
mkdirp.sync(join(pluginDir, 'plugin2'));
|
||||
mkdirp.sync(join(pluginDir, 'plugin3'));
|
||||
createPlugin('plugin1', '5.0.0-alpha2', pluginDir);
|
||||
createPlugin('plugin2', '3.2.1', pluginDir);
|
||||
createPlugin('plugin3', '1.2.3', pluginDir);
|
||||
|
||||
list(settings, logger);
|
||||
|
||||
expect(logger.log.calledWith('plugin1')).to.be(true);
|
||||
expect(logger.log.calledWith('plugin2')).to.be(true);
|
||||
expect(logger.log.calledWith('plugin3')).to.be(true);
|
||||
expect(logger.log.calledWith('plugin1@5.0.0-alpha2')).to.be(true);
|
||||
expect(logger.log.calledWith('plugin2@3.2.1')).to.be(true);
|
||||
expect(logger.log.calledWith('plugin3@1.2.3')).to.be(true);
|
||||
});
|
||||
|
||||
it('ignore folders that start with a period', function () {
|
||||
mkdirp.sync(join(pluginDir, '.foo'));
|
||||
mkdirp.sync(join(pluginDir, 'plugin1'));
|
||||
mkdirp.sync(join(pluginDir, 'plugin2'));
|
||||
mkdirp.sync(join(pluginDir, 'plugin3'));
|
||||
mkdirp.sync(join(pluginDir, '.bar'));
|
||||
createPlugin('.foo', '1.0.0', pluginDir);
|
||||
createPlugin('plugin1', '5.0.0-alpha2', pluginDir);
|
||||
createPlugin('plugin2', '3.2.1', pluginDir);
|
||||
createPlugin('plugin3', '1.2.3', pluginDir);
|
||||
createPlugin('.bar', '1.0.0', pluginDir);
|
||||
|
||||
list(settings, logger);
|
||||
|
||||
expect(logger.log.calledWith('.foo')).to.be(false);
|
||||
expect(logger.log.calledWith('.bar')).to.be(false);
|
||||
expect(logger.log.calledWith('.foo@1.0.0')).to.be(false);
|
||||
expect(logger.log.calledWith('.bar@1.0.0')).to.be(false);
|
||||
});
|
||||
|
||||
it('list should only list folders', function () {
|
||||
mkdirp.sync(join(pluginDir, 'plugin1'));
|
||||
mkdirp.sync(join(pluginDir, 'plugin2'));
|
||||
mkdirp.sync(join(pluginDir, 'plugin3'));
|
||||
createPlugin('plugin1', '1.0.0', pluginDir);
|
||||
createPlugin('plugin2', '1.0.0', pluginDir);
|
||||
createPlugin('plugin3', '1.0.0', pluginDir);
|
||||
writeFileSync(join(pluginDir, 'plugin4'), 'This is a file, and not a folder.');
|
||||
|
||||
list(settings, logger);
|
||||
|
||||
expect(logger.log.calledWith('plugin1')).to.be(true);
|
||||
expect(logger.log.calledWith('plugin2')).to.be(true);
|
||||
expect(logger.log.calledWith('plugin3')).to.be(true);
|
||||
expect(logger.log.calledWith('plugin1@1.0.0')).to.be(true);
|
||||
expect(logger.log.calledWith('plugin2@1.0.0')).to.be(true);
|
||||
expect(logger.log.calledWith('plugin3@1.0.0')).to.be(true);
|
||||
});
|
||||
|
||||
it('list should throw an exception if a plugin does not have a package.json', function () {
|
||||
createPlugin('plugin1', '1.0.0', pluginDir);
|
||||
mkdirp.sync(join(pluginDir, 'empty-plugin'));
|
||||
|
||||
expect(function () {
|
||||
list(settings, logger);
|
||||
}).to.throwError('Unable to read package.json file for plugin empty-plugin');
|
||||
});
|
||||
|
||||
it('list should throw an exception if a plugin have an empty package.json', function () {
|
||||
createPlugin('plugin1', '1.0.0', pluginDir);
|
||||
const invalidPluginDir = join(pluginDir, 'invalid-plugin');
|
||||
mkdirp.sync(invalidPluginDir);
|
||||
appendFileSync(join(invalidPluginDir, 'package.json'), '');
|
||||
|
||||
expect(function () {
|
||||
list(settings, logger);
|
||||
}).to.throwError('Unable to read package.json file for plugin invalid-plugin');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { statSync, readdirSync } from 'fs';
|
||||
import { statSync, readdirSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
export default function list(settings, logger) {
|
||||
|
@ -7,7 +7,13 @@ export default function list(settings, logger) {
|
|||
const stat = statSync(join(settings.pluginDir, filename));
|
||||
|
||||
if (stat.isDirectory() && filename[0] !== '.') {
|
||||
logger.log(filename);
|
||||
try {
|
||||
const packagePath = join(settings.pluginDir, filename, 'package.json');
|
||||
const { version } = JSON.parse(readFileSync(packagePath, 'utf8'));
|
||||
logger.log(filename + '@' + version);
|
||||
} catch (e) {
|
||||
throw new Error('Unable to read package.json file for plugin ' + filename);
|
||||
}
|
||||
}
|
||||
});
|
||||
logger.log(''); //intentional blank line for aesthetics
|
||||
|
|
|
@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||
|
||||
module.exports = {
|
||||
'valueFormatter': _.identity,
|
||||
'geohashGridAgg': { 'vis': { 'params': {} } },
|
||||
'geoJson': {
|
||||
'type': 'FeatureCollection',
|
||||
'features': [
|
||||
|
|
|
@ -9,7 +9,7 @@ function VisDetailsSpyProvider(Notifier, $filter, $rootScope, config) {
|
|||
template: visDebugSpyPanelTemplate,
|
||||
order: 5,
|
||||
link: function ($scope, $el) {
|
||||
$scope.$watch('vis.getState() | json', function (json) {
|
||||
$scope.$watch('vis.getEnabledState() | json', function (json) {
|
||||
$scope.visStateJson = json;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ export default function TileMapVisType(Private, getAppState, courier, config) {
|
|||
heatRadius: 25,
|
||||
heatBlur: 15,
|
||||
heatNormalizeData: true,
|
||||
mapZoom: 2,
|
||||
mapCenter: [15, 5],
|
||||
wms: config.get('visualization:tileMap:WMSdefaults')
|
||||
},
|
||||
mapTypes: ['Scaled Circle Markers', 'Shaded Circle Markers', 'Shaded Geohash Grid', 'Heatmap'],
|
||||
|
@ -46,54 +48,16 @@ export default function TileMapVisType(Private, getAppState, courier, config) {
|
|||
|
||||
pushFilter(filter, false, indexPatternName);
|
||||
},
|
||||
mapMoveEnd: function (event) {
|
||||
const agg = _.get(event, 'chart.geohashGridAgg');
|
||||
if (!agg) return;
|
||||
|
||||
agg.params.mapZoom = event.zoom;
|
||||
agg.params.mapCenter = [event.center.lat, event.center.lng];
|
||||
|
||||
const editableVis = agg.vis.getEditableVis();
|
||||
if (!editableVis) return;
|
||||
|
||||
const editableAgg = editableVis.aggs.byId[agg.id];
|
||||
if (editableAgg) {
|
||||
editableAgg.params.mapZoom = event.zoom;
|
||||
editableAgg.params.mapCenter = [event.center.lat, event.center.lng];
|
||||
}
|
||||
mapMoveEnd: function (event, uiState) {
|
||||
uiState.set('mapCenter', event.center);
|
||||
},
|
||||
mapZoomEnd: function (event) {
|
||||
const agg = _.get(event, 'chart.geohashGridAgg');
|
||||
if (!agg || !agg.params.autoPrecision) return;
|
||||
mapZoomEnd: function (event, uiState) {
|
||||
uiState.set('mapZoom', event.zoom);
|
||||
|
||||
// zoomPrecision maps event.zoom to a geohash precision value
|
||||
// event.limit is the configurable max geohash precision
|
||||
// default max precision is 7, configurable up to 12
|
||||
const zoomPrecision = {
|
||||
1: 2,
|
||||
2: 2,
|
||||
3: 2,
|
||||
4: 3,
|
||||
5: 3,
|
||||
6: 4,
|
||||
7: 4,
|
||||
8: 5,
|
||||
9: 5,
|
||||
10: 6,
|
||||
11: 6,
|
||||
12: 7,
|
||||
13: 7,
|
||||
14: 8,
|
||||
15: 9,
|
||||
16: 10,
|
||||
17: 11,
|
||||
18: 12
|
||||
};
|
||||
|
||||
const precision = config.get('visualization:tileMap:maxPrecision');
|
||||
agg.params.precision = Math.min(zoomPrecision[event.zoom], precision);
|
||||
|
||||
courier.fetch();
|
||||
const autoPrecision = _.get(event, 'chart.geohashGridAgg.params.autoPrecision');
|
||||
if (autoPrecision) {
|
||||
courier.fetch();
|
||||
}
|
||||
}
|
||||
},
|
||||
responseConverter: geoJsonConverter,
|
||||
|
|
|
@ -55,6 +55,10 @@ uiModules
|
|||
// create child ui state from the savedObj
|
||||
const uiState = panelConfig.uiState || {};
|
||||
$scope.uiState = $scope.parentUiState.createChild(getPanelId(panelConfig.panel), uiState, true);
|
||||
const panelSavedVis = _.get(panelConfig, 'savedObj.vis'); // Sometimes this will be a search, and undef
|
||||
if (panelSavedVis) {
|
||||
panelSavedVis.setUiState($scope.uiState);
|
||||
}
|
||||
|
||||
$scope.filter = function (field, value, operator) {
|
||||
const index = $scope.savedObj.searchSource.get('index').id;
|
||||
|
|
|
@ -496,7 +496,7 @@ app.controller('discover', function ($scope, config, courier, $route, $window, N
|
|||
|
||||
// we have a vis, just modify the aggs
|
||||
if ($scope.vis) {
|
||||
const visState = $scope.vis.getState();
|
||||
const visState = $scope.vis.getEnabledState();
|
||||
visState.aggs = visStateAggs;
|
||||
|
||||
$scope.vis.setState(visState);
|
||||
|
|
13
src/plugins/kibana/public/discover/directives/no_results.js
Normal file
13
src/plugins/kibana/public/discover/directives/no_results.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import uiModules from 'ui/modules';
|
||||
import noResultsTemplate from '../partials/no_results.html';
|
||||
|
||||
uiModules
|
||||
.get('apps/discover')
|
||||
.directive('discoverNoResults', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: noResultsTemplate
|
||||
};
|
||||
});
|
|
@ -56,58 +56,7 @@
|
|||
|
||||
<div class="discover-wrapper col-md-10">
|
||||
<div class="discover-content">
|
||||
<!-- no results -->
|
||||
<div ng-show="resultState === 'none'">
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
|
||||
<h1>No results found <i aria-hidden="true" class="fa fa-meh-o"></i></h1>
|
||||
|
||||
<p>
|
||||
Unfortunately I could not find any results matching your search. I tried really hard. I looked all over the place and frankly, I just couldn't find anything good. Help me, help you. Here are some ideas:
|
||||
</p>
|
||||
|
||||
<div class="shard-failures" ng-show="failures">
|
||||
<h3>Shard Failures</h3>
|
||||
<p>The following shard failures ocurred:</p>
|
||||
<ul>
|
||||
<li ng-repeat="failure in failures | limitTo: failuresShown"><strong>Index:</strong> {{failure.index}} <strong>Shard:</strong> {{failure.shard}} <strong>Reason:</strong> {{failure.reason}} </li>
|
||||
</ul>
|
||||
<a ng-click="showAllFailures()" ng-if="failures.length > failuresShown" title="Show More">Show More</a>
|
||||
<a ng-click="showLessFailures()" ng-if="failures.length === failuresShown && failures.length > 5" title="Show Less">Show Less</a>
|
||||
</div>
|
||||
|
||||
<div ng-show="opts.timefield">
|
||||
<p>
|
||||
<h3>Expand your time range</h3>
|
||||
<p>I see you are looking at an index with a date field. It is possible your query does not match anything in the current time range, or that there is no data at all in the currently selected time range. Try selecting a wider time range by opening the time picker <i aria-hidden="true" class="fa fa-clock-o"></i> in the top right corner of your screen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3>Refine your query</h3>
|
||||
<p>
|
||||
The search bar at the top uses Elasticsearch's support for Lucene <a href="http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax" target="_blank">Query String syntax</a>. Let's say we're searching web server logs that have been parsed into a few fields.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h4>Examples:</h4>
|
||||
Find requests that contain the number 200, in any field:
|
||||
<pre>200</pre>
|
||||
|
||||
Or we can search in a specific field. Find 200 in the status field:
|
||||
<pre>status:200</pre>
|
||||
|
||||
Find all status codes between 400-499:
|
||||
<pre>status:[400 TO 499]</pre>
|
||||
|
||||
Find status codes 400-499 with the extension php:
|
||||
<pre>status:[400 TO 499] AND extension:PHP</pre>
|
||||
|
||||
Or HTML
|
||||
<pre>status:[400 TO 499] AND (extension:php OR extension:html)</pre>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<discover-no-results ng-show="resultState === 'none'"></discover-no-results>
|
||||
|
||||
<!-- loading -->
|
||||
<div ng-show="resultState === 'loading'">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'plugins/kibana/discover/saved_searches/saved_searches';
|
||||
import 'plugins/kibana/discover/directives/no_results';
|
||||
import 'plugins/kibana/discover/directives/timechart';
|
||||
import 'ui/navbar_extensions';
|
||||
import 'ui/collapsible_sidebar';
|
||||
|
|
51
src/plugins/kibana/public/discover/partials/no_results.html
Normal file
51
src/plugins/kibana/public/discover/partials/no_results.html
Normal file
|
@ -0,0 +1,51 @@
|
|||
<div>
|
||||
<div class="col-md-10 col-md-offset-1" data-test-subj="discoverNoResults">
|
||||
|
||||
<h1>No results found <i aria-hidden="true" class="fa fa-meh-o"></i></h1>
|
||||
|
||||
<p>
|
||||
Unfortunately I could not find any results matching your search. I tried really hard. I looked all over the place and frankly, I just couldn't find anything good. Help me, help you. Here are some ideas:
|
||||
</p>
|
||||
|
||||
<div class="shard-failures" ng-show="failures">
|
||||
<h3>Shard Failures</h3>
|
||||
<p>The following shard failures ocurred:</p>
|
||||
<ul>
|
||||
<li ng-repeat="failure in failures | limitTo: failuresShown"><strong>Index:</strong> {{failure.index}} <strong>Shard:</strong> {{failure.shard}} <strong>Reason:</strong> {{failure.reason}} </li>
|
||||
</ul>
|
||||
<a ng-click="showAllFailures()" ng-if="failures.length > failuresShown" title="Show More">Show More</a>
|
||||
<a ng-click="showLessFailures()" ng-if="failures.length === failuresShown && failures.length > 5" title="Show Less">Show Less</a>
|
||||
</div>
|
||||
|
||||
<div ng-show="opts.timefield">
|
||||
<p>
|
||||
<h3>Expand your time range</h3>
|
||||
<p>I see you are looking at an index with a date field. It is possible your query does not match anything in the current time range, or that there is no data at all in the currently selected time range. Click the button below to open the time picker. For future reference you can open the time picker by clicking on the <a class="btn btn-xs navbtn" ng-click="kbnTopNav.toggle('filter')" aria-expanded="kbnTopNav.is('filter')" aria-label="time picker" data-test-subj="discoverNoResultsTimefilter"><i aria-hidden="true" class="fa fa-clock-o"></i> time picker</a> button in the top right corner of your screen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3>Refine your query</h3>
|
||||
<p>
|
||||
The search bar at the top uses Elasticsearch's support for Lucene <a href="http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax" target="_blank">Query String syntax</a>. Let's say we're searching web server logs that have been parsed into a few fields.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<h4>Examples:</h4>
|
||||
Find requests that contain the number 200, in any field:
|
||||
<pre>200</pre>
|
||||
|
||||
Or we can search in a specific field. Find 200 in the status field:
|
||||
<pre>status:200</pre>
|
||||
|
||||
Find all status codes between 400-499:
|
||||
<pre>status:[400 TO 499]</pre>
|
||||
|
||||
Find status codes 400-499 with the extension php:
|
||||
<pre>status:[400 TO 499] AND extension:PHP</pre>
|
||||
|
||||
Or HTML
|
||||
<pre>status:[400 TO 499] AND (extension:php OR extension:html)</pre>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
|
@ -83,9 +83,12 @@ uiModules.get('apps/settings')
|
|||
};
|
||||
|
||||
$scope.bulkDelete = function () {
|
||||
$scope.currentTab.service.delete(pluck($scope.selectedItems, 'id')).then(refreshData).then(function () {
|
||||
$scope.currentTab.service.delete(pluck($scope.selectedItems, 'id'))
|
||||
.then(refreshData)
|
||||
.then(function () {
|
||||
$scope.selectedItems.length = 0;
|
||||
});
|
||||
})
|
||||
.catch(error => notify.error(error));
|
||||
};
|
||||
|
||||
$scope.bulkExport = function () {
|
||||
|
|
|
@ -204,3 +204,5 @@ kbn-settings-indices {
|
|||
.kbn-settings-indices-create {
|
||||
.time-and-pattern > div {}
|
||||
}
|
||||
|
||||
@import "~ui/dragula/gu-dragula.less";
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
import angular from 'angular';
|
||||
import sinon from 'sinon';
|
||||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
|
||||
let init;
|
||||
let $rootScope;
|
||||
let $compile;
|
||||
|
||||
describe('draggable_* directives', function () {
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function ($injector) {
|
||||
$rootScope = $injector.get('$rootScope');
|
||||
$compile = $injector.get('$compile');
|
||||
init = function init(markup = '') {
|
||||
const $parentScope = $rootScope.$new();
|
||||
$parentScope.items = [
|
||||
{ name: 'item_1' },
|
||||
{ name: 'item_2' },
|
||||
{ name: 'item_3' }
|
||||
];
|
||||
|
||||
// create the markup
|
||||
const $elem = angular.element(`<div draggable-container="items">`);
|
||||
$elem.html(markup);
|
||||
|
||||
// compile the directive
|
||||
$compile($elem)($parentScope);
|
||||
$parentScope.$apply();
|
||||
|
||||
const $scope = $elem.scope();
|
||||
|
||||
return { $parentScope, $scope, $elem };
|
||||
};
|
||||
}));
|
||||
|
||||
describe('draggable_container directive', function () {
|
||||
it('should expose the drake', function () {
|
||||
const { $scope } = init();
|
||||
expect($scope.drake).to.be.an(Object);
|
||||
});
|
||||
|
||||
it('should expose the controller', function () {
|
||||
const { $scope } = init();
|
||||
expect($scope.draggableContainerCtrl).to.be.an(Object);
|
||||
});
|
||||
|
||||
it('should pull item list from directive attribute', function () {
|
||||
const { $scope, $parentScope } = init();
|
||||
expect($scope.draggableContainerCtrl.getList()).to.eql($parentScope.items);
|
||||
});
|
||||
|
||||
it('should not be able to move extraneous DOM elements', function () {
|
||||
const bare = angular.element(`<div>`);
|
||||
const { $scope } = init();
|
||||
expect($scope.drake.canMove(bare[0])).to.eql(false);
|
||||
});
|
||||
|
||||
it('should not be able to move non-[draggable-item] elements', function () {
|
||||
const bare = angular.element(`<div>`);
|
||||
const { $scope, $elem } = init();
|
||||
$elem.append(bare);
|
||||
expect($scope.drake.canMove(bare[0])).to.eql(false);
|
||||
});
|
||||
|
||||
it('shouldn\'t be able to move extraneous [draggable-item] elements', function () {
|
||||
const anotherParent = angular.element(`<div draggable-container="items">`);
|
||||
const item = angular.element(`<div draggable-item="items[0]">`);
|
||||
const scope = $rootScope.$new();
|
||||
anotherParent.append(item);
|
||||
$compile(anotherParent)(scope);
|
||||
$compile(item)(scope);
|
||||
scope.$apply();
|
||||
const { $scope } = init();
|
||||
expect($scope.drake.canMove(item[0])).to.eql(false);
|
||||
});
|
||||
|
||||
it('shouldn\'t be able to move [draggable-item] if it has a handle', function () {
|
||||
const { $scope, $elem } = init(`
|
||||
<div draggable-item="items[0]">
|
||||
<div draggable-handle></div>
|
||||
</div>
|
||||
`);
|
||||
const item = $elem.find(`[draggable-item]`);
|
||||
expect($scope.drake.canMove(item[0])).to.eql(false);
|
||||
});
|
||||
|
||||
it('should be able to move [draggable-item] by its handle', function () {
|
||||
const { $scope, $elem } = init(`
|
||||
<div draggable-item="items[0]">
|
||||
<div draggable-handle></div>
|
||||
</div>
|
||||
`);
|
||||
const handle = $elem.find(`[draggable-handle]`);
|
||||
expect($scope.drake.canMove(handle[0])).to.eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('draggable_item', function () {
|
||||
it('should be required to be a child to [draggable-container]', function () {
|
||||
const item = angular.element(`<div draggable-item="items[0]">`);
|
||||
const scope = $rootScope.$new();
|
||||
expect(() => {
|
||||
$compile(item)(scope);
|
||||
scope.$apply();
|
||||
}).to.throwException(/controller(.+)draggableContainer(.+)required/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('draggable_handle', function () {
|
||||
it('should be required to be a child to [draggable-item]', function () {
|
||||
const handle = angular.element(`<div draggable-handle>`);
|
||||
const scope = $rootScope.$new();
|
||||
expect(() => {
|
||||
$compile(handle)(scope);
|
||||
scope.$apply();
|
||||
}).to.throwException(/controller(.+)draggableItem(.+)required/i);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -27,30 +27,40 @@
|
|||
|
||||
<!-- controls !!!actually disabling buttons will break tooltips¡¡¡ -->
|
||||
<div class="vis-editor-agg-header-controls btn-group">
|
||||
<!-- up button -->
|
||||
<!-- disable aggregation -->
|
||||
<button
|
||||
aria-label="Increase Priority"
|
||||
ng-if="stats.count > 1"
|
||||
ng-class="{ disabled: $first }"
|
||||
ng-click="moveUp(agg)"
|
||||
tooltip="Increase Priority"
|
||||
ng-if="agg.enabled && canRemove(agg)"
|
||||
ng-click="agg.enabled = false"
|
||||
aria-label="Disable aggregation"
|
||||
tooltip="Disable aggregation"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
class="btn btn-xs btn-primary">
|
||||
<i aria-hidden="true" class="fa fa-caret-up"></i>
|
||||
class="btn btn-xs">
|
||||
<i aria-hidden="true" class="fa fa-toggle-on"></i>
|
||||
</button>
|
||||
|
||||
<!-- down button -->
|
||||
<!-- enable aggregation -->
|
||||
<button
|
||||
aria-label="Decrease Priority"
|
||||
ng-if="stats.count > 1"
|
||||
ng-class="{ disabled: $last }"
|
||||
ng-click="moveDown(agg)"
|
||||
tooltip="Decrease Priority"
|
||||
ng-if="!agg.enabled"
|
||||
ng-click="agg.enabled = true"
|
||||
aria-label="Enable aggregation"
|
||||
tooltip="Enable aggregation"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
class="btn btn-xs btn-primary">
|
||||
<i aria-hidden="true" class="fa fa-caret-down"></i>
|
||||
class="btn btn-xs">
|
||||
<i aria-hidden="true" class="fa fa-toggle-off"></i>
|
||||
</button>
|
||||
|
||||
<!-- drag handle -->
|
||||
<button
|
||||
draggable-handle
|
||||
aria-label="Modify Priority by Dragging"
|
||||
ng-if="stats.count > 1"
|
||||
tooltip="Modify Priority by Dragging"
|
||||
tooltip-append-to-body="true"
|
||||
type="button"
|
||||
class="btn btn-xs">
|
||||
<i aria-hidden="true" class="fa fa-arrows-v"></i>
|
||||
</button>
|
||||
|
||||
<!-- remove button -->
|
||||
|
@ -79,5 +89,6 @@
|
|||
|
||||
<vis-editor-agg-add
|
||||
ng-if="$index + 1 === stats.count"
|
||||
ng-hide="dragging"
|
||||
class="vis-editor-agg-add vis-editor-agg-add-subagg">
|
||||
</vis-editor-agg-add>
|
||||
|
|
|
@ -21,7 +21,6 @@ uiModules
|
|||
template: aggTemplate,
|
||||
require: 'form',
|
||||
link: function ($scope, $el, attrs, kbnForm) {
|
||||
$scope.$bind('outputAgg', 'outputVis.aggs.byId[agg.id]', $scope);
|
||||
$scope.editorOpen = !!$scope.agg.brandNew;
|
||||
|
||||
$scope.$watch('editorOpen', function (open) {
|
||||
|
@ -47,13 +46,16 @@ uiModules
|
|||
return label ? label : '';
|
||||
};
|
||||
|
||||
function move(below, agg) {
|
||||
_.move($scope.vis.aggs, agg, below, function (otherAgg) {
|
||||
return otherAgg.schema.group === agg.schema.group;
|
||||
});
|
||||
}
|
||||
$scope.moveUp = _.partial(move, false);
|
||||
$scope.moveDown = _.partial(move, true);
|
||||
$scope.$on('drag-start', e => {
|
||||
$scope.editorWasOpen = $scope.editorOpen;
|
||||
$scope.editorOpen = false;
|
||||
$scope.$emit('agg-drag-start', $scope.agg);
|
||||
});
|
||||
|
||||
$scope.$on('drag-end', e => {
|
||||
$scope.editorOpen = $scope.editorWasOpen;
|
||||
$scope.$emit('agg-drag-end', $scope.agg);
|
||||
});
|
||||
|
||||
$scope.remove = function (agg) {
|
||||
const aggs = $scope.vis.aggs;
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
{{ groupName }}
|
||||
</div>
|
||||
|
||||
<div class="vis-editor-agg-group" ng-class="groupName">
|
||||
<div ng-class="groupName" draggable-container="vis.aggs" class="vis-editor-agg-group">
|
||||
<!-- wrapper needed for nesting-indicator -->
|
||||
<div ng-repeat="agg in group" class="vis-editor-agg-wrapper">
|
||||
<div ng-repeat="agg in group" draggable-item="agg" class="vis-editor-agg-wrapper">
|
||||
<!-- agg.html - controls for aggregation -->
|
||||
<ng-form vis-editor-agg name="aggForm" class="vis-editor-agg"></ng-form>
|
||||
</div>
|
||||
|
|
|
@ -41,6 +41,9 @@ uiModules
|
|||
if (count < schema.max) return true;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.$on('agg-drag-start', e => $scope.dragging = true);
|
||||
$scope.$on('agg-drag-end', e => $scope.dragging = false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import dragula from 'dragula';
|
||||
import uiModules from 'ui/modules';
|
||||
|
||||
uiModules
|
||||
.get('app/visualize')
|
||||
.directive('draggableContainer', function () {
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: true,
|
||||
controllerAs: 'draggableContainerCtrl',
|
||||
controller($scope, $attrs, $parse) {
|
||||
this.getList = () => $parse($attrs.draggableContainer)($scope);
|
||||
},
|
||||
link($scope, $el, attr) {
|
||||
const drake = dragula({
|
||||
containers: $el.toArray(),
|
||||
moves(el, source, handle) {
|
||||
const itemScope = $(el).scope();
|
||||
if (!('draggableItemCtrl' in itemScope)) {
|
||||
return; // only [draggable-item] is draggable
|
||||
}
|
||||
return itemScope.draggableItemCtrl.moves(handle);
|
||||
}
|
||||
});
|
||||
|
||||
const drakeEvents = [
|
||||
'cancel',
|
||||
'cloned',
|
||||
'drag',
|
||||
'dragend',
|
||||
'drop',
|
||||
'out',
|
||||
'over',
|
||||
'remove',
|
||||
'shadow'
|
||||
];
|
||||
const prettifiedDrakeEvents = {
|
||||
drag: 'start',
|
||||
dragend: 'end'
|
||||
};
|
||||
|
||||
drakeEvents.forEach(type => {
|
||||
drake.on(type, (el, ...args) => forwardEvent(type, el, ...args));
|
||||
});
|
||||
drake.on('drag', markDragging(true));
|
||||
drake.on('dragend', markDragging(false));
|
||||
drake.on('drop', drop);
|
||||
$scope.$on('$destroy', drake.destroy);
|
||||
$scope.drake = drake;
|
||||
|
||||
function markDragging(isDragging) {
|
||||
return el => {
|
||||
const scope = $(el).scope();
|
||||
scope.isDragging = isDragging;
|
||||
scope.$apply();
|
||||
};
|
||||
}
|
||||
|
||||
function forwardEvent(type, el, ...args) {
|
||||
const name = `drag-${prettifiedDrakeEvents[type] || type}`;
|
||||
const scope = $(el).scope();
|
||||
scope.$broadcast(name, el, ...args);
|
||||
}
|
||||
|
||||
function drop(el, target, source, sibling) {
|
||||
const list = $scope.draggableContainerCtrl.getList();
|
||||
const itemScope = $(el).scope();
|
||||
const item = itemScope.draggableItemCtrl.getItem();
|
||||
const toIndex = getSiblingItemIndex(list, sibling);
|
||||
_.move(list, item, toIndex);
|
||||
}
|
||||
|
||||
function getSiblingItemIndex(list, sibling) {
|
||||
if (!sibling) { // means the item was dropped at the end of the list
|
||||
return list.length - 1;
|
||||
}
|
||||
const siblingScope = $(sibling).scope();
|
||||
const siblingItem = siblingScope.draggableItemCtrl.getItem();
|
||||
const siblingIndex = list.indexOf(siblingItem);
|
||||
return siblingIndex;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
import uiModules from 'ui/modules';
|
||||
|
||||
uiModules
|
||||
.get('app/visualize')
|
||||
.directive('draggableHandle', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '^draggableItem',
|
||||
link($scope, $el, attr, ctrl) {
|
||||
ctrl.registerHandle($el);
|
||||
$el.addClass('gu-handle');
|
||||
}
|
||||
};
|
||||
});
|
29
src/plugins/kibana/public/visualize/editor/draggable_item.js
Normal file
29
src/plugins/kibana/public/visualize/editor/draggable_item.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import $ from 'jquery';
|
||||
import uiModules from 'ui/modules';
|
||||
|
||||
uiModules
|
||||
.get('app/visualize')
|
||||
.directive('draggableItem', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: '^draggableContainer',
|
||||
scope: true,
|
||||
controllerAs: 'draggableItemCtrl',
|
||||
controller($scope, $attrs, $parse) {
|
||||
const dragHandles = $();
|
||||
|
||||
this.getItem = () => $parse($attrs.draggableItem)($scope);
|
||||
this.registerHandle = $el => {
|
||||
dragHandles.push(...$el);
|
||||
};
|
||||
this.moves = handle => {
|
||||
const $handle = $(handle);
|
||||
const $anywhereInParentChain = $handle.parents().addBack();
|
||||
const movable = dragHandles.is($anywhereInParentChain);
|
||||
return movable;
|
||||
};
|
||||
},
|
||||
link($scope, $el, attr) {
|
||||
}
|
||||
};
|
||||
});
|
|
@ -119,8 +119,8 @@ uiModules
|
|||
|
||||
if (!angular.equals($state.vis, savedVisState)) {
|
||||
Promise.try(function () {
|
||||
vis.setState($state.vis);
|
||||
editableVis.setState($state.vis);
|
||||
vis.setState(editableVis.getEnabledState());
|
||||
})
|
||||
.catch(courier.redirectWhenMissing({
|
||||
'index-pattern-field': '/visualize'
|
||||
|
@ -139,6 +139,7 @@ uiModules
|
|||
$scope.editableVis = editableVis;
|
||||
$scope.state = $state;
|
||||
$scope.uiState = $state.makeStateful('uiState');
|
||||
vis.setUiState($scope.uiState);
|
||||
$scope.timefilter = timefilter;
|
||||
$scope.opts = _.pick($scope, 'doSave', 'savedVis', 'shareData', 'timefilter');
|
||||
|
||||
|
@ -149,9 +150,9 @@ uiModules
|
|||
$scope.stageEditableVis = transferVisState(editableVis, vis, true);
|
||||
$scope.resetEditableVis = transferVisState(vis, editableVis);
|
||||
$scope.$watch(function () {
|
||||
return editableVis.getState();
|
||||
return editableVis.getEnabledState();
|
||||
}, function (newState) {
|
||||
editableVis.dirty = !angular.equals(newState, vis.getState());
|
||||
editableVis.dirty = !angular.equals(newState, vis.getEnabledState());
|
||||
|
||||
$scope.responseValueAggs = null;
|
||||
try {
|
||||
|
@ -291,14 +292,16 @@ uiModules
|
|||
}
|
||||
};
|
||||
|
||||
function transferVisState(fromVis, toVis, fetch) {
|
||||
function transferVisState(fromVis, toVis, stage) {
|
||||
return function () {
|
||||
toVis.setState(fromVis.getState());
|
||||
const view = fromVis.getEnabledState();
|
||||
const full = fromVis.getState();
|
||||
toVis.setState(view);
|
||||
editableVis.dirty = false;
|
||||
$state.vis = vis.getState();
|
||||
$state.vis = full;
|
||||
$state.save();
|
||||
|
||||
if (fetch) $scope.fetch();
|
||||
if (stage) $scope.fetch();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ uiModules
|
|||
controllerAs: 'sidebar',
|
||||
controller: function ($scope) {
|
||||
$scope.$bind('vis', 'editableVis');
|
||||
$scope.$bind('outputVis', 'vis');
|
||||
$scope.$watch('vis.type', (visType) => {
|
||||
if (visType) {
|
||||
this.showData = visType.schemas.buckets || visType.schemas.metrics;
|
||||
|
|
|
@ -11,6 +11,9 @@ import 'plugins/kibana/visualize/editor/agg_params';
|
|||
import 'plugins/kibana/visualize/editor/nesting_indicator';
|
||||
import 'plugins/kibana/visualize/editor/sidebar';
|
||||
import 'plugins/kibana/visualize/editor/vis_options';
|
||||
import 'plugins/kibana/visualize/editor/draggable_container';
|
||||
import 'plugins/kibana/visualize/editor/draggable_item';
|
||||
import 'plugins/kibana/visualize/editor/draggable_handle';
|
||||
import 'plugins/kibana/visualize/saved_visualizations/_saved_vis';
|
||||
import 'plugins/kibana/visualize/saved_visualizations/saved_visualizations';
|
||||
import uiRoutes from 'ui/routes';
|
||||
|
|
|
@ -25,14 +25,16 @@
|
|||
No plugin status information available
|
||||
</h4>
|
||||
|
||||
<table class="plugin_status_breakdown row" ng-if="ui.statuses">
|
||||
<tr>
|
||||
<th class="col-xs-1">Name</th>
|
||||
<th class="col-xs-11">Status</th>
|
||||
<table class="plugin_status_breakdown" ng-if="ui.statuses">
|
||||
<tr class="row">
|
||||
<th class="col-xs-2">Name</th>
|
||||
<th class="col-xs-2">Version</th>
|
||||
<th class="col-xs-8">Status</th>
|
||||
</tr>
|
||||
<tr ng-repeat="status in ui.statuses" class="status_row plugin_state_default plugin_state_{{status.state}}">
|
||||
<td class="col-xs-1 status_name">{{status.name}}</td>
|
||||
<td class="col-xs-11 status_message">
|
||||
<tr ng-repeat="status in ui.statuses" class="status_row plugin_state_default plugin_state_{{status.state}} row">
|
||||
<td class="col-xs-2 status_name">{{status.name}}</td>
|
||||
<td class="col-xs-2 status_version">{{status.version}}</td>
|
||||
<td class="col-xs-8 status_message">
|
||||
<i class="fa plugin_state_color plugin_state_icon" />
|
||||
{{status.message}}
|
||||
</td>
|
||||
|
|
|
@ -3,6 +3,8 @@ import { constant, once, compact, flatten } from 'lodash';
|
|||
import { promisify, resolve, fromNode } from 'bluebird';
|
||||
import { isWorker } from 'cluster';
|
||||
import { fromRoot, pkg } from '../utils';
|
||||
import Config from './config/config';
|
||||
import loggingConfiguration from './logging/configuration';
|
||||
|
||||
let rootDir = fromRoot('.');
|
||||
|
||||
|
@ -107,4 +109,16 @@ module.exports = class KbnServer {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
applyLoggingConfiguration(settings) {
|
||||
const config = Config.withDefaultSchema(settings);
|
||||
const loggingOptions = loggingConfiguration(config);
|
||||
const subset = {
|
||||
ops: config.get('ops'),
|
||||
logging: config.get('logging')
|
||||
};
|
||||
const plain = JSON.stringify(subset, null, 2);
|
||||
this.server.log(['info', 'config'], 'New logging configuration:\n' + plain);
|
||||
this.server.plugins['even-better'].monitor.reconfigure(loggingOptions);
|
||||
}
|
||||
};
|
||||
|
|
61
src/server/logging/configuration.js
Normal file
61
src/server/logging/configuration.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
import _ from 'lodash';
|
||||
import logReporter from './log_reporter';
|
||||
|
||||
export default function loggingConfiguration(config) {
|
||||
let events = config.get('logging.events');
|
||||
|
||||
if (config.get('logging.silent')) {
|
||||
_.defaults(events, {});
|
||||
}
|
||||
else if (config.get('logging.quiet')) {
|
||||
_.defaults(events, {
|
||||
log: ['listening', 'error', 'fatal'],
|
||||
request: ['error'],
|
||||
error: '*'
|
||||
});
|
||||
}
|
||||
else if (config.get('logging.verbose')) {
|
||||
_.defaults(events, {
|
||||
log: '*',
|
||||
ops: '*',
|
||||
request: '*',
|
||||
response: '*',
|
||||
error: '*'
|
||||
});
|
||||
}
|
||||
else {
|
||||
_.defaults(events, {
|
||||
log: ['info', 'warning', 'error', 'fatal'],
|
||||
response: config.get('logging.json') ? '*' : '!',
|
||||
request: ['info', 'warning', 'error', 'fatal'],
|
||||
error: '*'
|
||||
});
|
||||
}
|
||||
|
||||
const options = {
|
||||
opsInterval: config.get('ops.interval'),
|
||||
requestHeaders: true,
|
||||
requestPayload: true,
|
||||
reporters: [
|
||||
{
|
||||
reporter: logReporter,
|
||||
config: {
|
||||
json: config.get('logging.json'),
|
||||
dest: config.get('logging.dest'),
|
||||
// I'm adding the default here because if you add another filter
|
||||
// using the commandline it will remove authorization. I want users
|
||||
// to have to explicitly set --logging.filter.authorization=none to
|
||||
// have it show up int he logs.
|
||||
filter: _.defaults(config.get('logging.filter'), {
|
||||
authorization: 'remove'
|
||||
})
|
||||
},
|
||||
events: _.transform(events, function (filtered, val, key) {
|
||||
// provide a string compatible way to remove events
|
||||
if (val !== '!') filtered[key] = val;
|
||||
}, {})
|
||||
}
|
||||
]
|
||||
};
|
||||
return options;
|
||||
}
|
|
@ -1,68 +1,15 @@
|
|||
import _ from 'lodash';
|
||||
import { fromNode } from 'bluebird';
|
||||
import evenBetter from 'even-better';
|
||||
import loggingConfiguration from './configuration';
|
||||
|
||||
module.exports = function (kbnServer, server, config) {
|
||||
export default function (kbnServer, server, config) {
|
||||
// prevent relying on kbnServer so this can be used with other hapi servers
|
||||
kbnServer = null;
|
||||
|
||||
return fromNode(function (cb) {
|
||||
let events = config.get('logging.events');
|
||||
|
||||
if (config.get('logging.silent')) {
|
||||
_.defaults(events, {});
|
||||
}
|
||||
else if (config.get('logging.quiet')) {
|
||||
_.defaults(events, {
|
||||
log: ['listening', 'error', 'fatal'],
|
||||
request: ['error'],
|
||||
error: '*'
|
||||
});
|
||||
}
|
||||
else if (config.get('logging.verbose')) {
|
||||
_.defaults(events, {
|
||||
log: '*',
|
||||
ops: '*',
|
||||
request: '*',
|
||||
response: '*',
|
||||
error: '*'
|
||||
});
|
||||
}
|
||||
else {
|
||||
_.defaults(events, {
|
||||
log: ['info', 'warning', 'error', 'fatal'],
|
||||
response: config.get('logging.json') ? '*' : '!',
|
||||
request: ['info', 'warning', 'error', 'fatal'],
|
||||
error: '*'
|
||||
});
|
||||
}
|
||||
|
||||
server.register({
|
||||
register: require('good'),
|
||||
options: {
|
||||
opsInterval: config.get('ops.interval'),
|
||||
requestHeaders: true,
|
||||
requestPayload: true,
|
||||
reporters: [
|
||||
{
|
||||
reporter: require('./log_reporter'),
|
||||
config: {
|
||||
json: config.get('logging.json'),
|
||||
dest: config.get('logging.dest'),
|
||||
// I'm adding the default here because if you add another filter
|
||||
// using the commandline it will remove authorization. I want users
|
||||
// to have to explicitly set --logging.filter.authorization=none to
|
||||
// have it show up int he logs.
|
||||
filter: _.defaults(config.get('logging.filter'), {
|
||||
authorization: 'remove'
|
||||
})
|
||||
},
|
||||
events: _.transform(events, function (filtered, val, key) {
|
||||
// provide a string compatible way to remove events
|
||||
if (val !== '!') filtered[key] = val;
|
||||
}, {})
|
||||
}
|
||||
]
|
||||
}
|
||||
register: evenBetter,
|
||||
options: loggingConfiguration(config)
|
||||
}, cb);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ let typeColors = {
|
|||
req: 'green',
|
||||
res: 'green',
|
||||
ops: 'cyan',
|
||||
config: 'cyan',
|
||||
err: 'red',
|
||||
info: 'green',
|
||||
error: 'red',
|
||||
|
|
|
@ -119,7 +119,7 @@ module.exports = class Plugin {
|
|||
}));
|
||||
|
||||
server.log(['plugins', 'debug'], {
|
||||
tmpl: 'Initializing plugin <%= plugin.id %>',
|
||||
tmpl: 'Initializing plugin <%= plugin.toString() %>',
|
||||
plugin: this
|
||||
});
|
||||
|
||||
|
@ -127,7 +127,7 @@ module.exports = class Plugin {
|
|||
server.exposeStaticDir(`/plugins/${id}/{path*}`, this.publicDir);
|
||||
}
|
||||
|
||||
this.status = kbnServer.status.create(`plugin:${this.id}`);
|
||||
this.status = kbnServer.status.create(this);
|
||||
server.expose('status', this.status);
|
||||
|
||||
return await attempt(this.externalInit, [server, options], this);
|
||||
|
|
|
@ -7,6 +7,8 @@ import Status from '../status';
|
|||
import ServerStatus from '../server_status';
|
||||
|
||||
describe('ServerStatus class', function () {
|
||||
const plugin = {id: 'name', version: '1.2.3'};
|
||||
|
||||
let server;
|
||||
let serverStatus;
|
||||
|
||||
|
@ -15,23 +17,23 @@ describe('ServerStatus class', function () {
|
|||
serverStatus = new ServerStatus(server);
|
||||
});
|
||||
|
||||
describe('#create(name)', function () {
|
||||
it('should create a new status by name', function () {
|
||||
let status = serverStatus.create('name');
|
||||
describe('#create(plugin)', function () {
|
||||
it('should create a new status by plugin', function () {
|
||||
let status = serverStatus.create(plugin);
|
||||
expect(status).to.be.a(Status);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#get(name)', function () {
|
||||
it('exposes plugins by name', function () {
|
||||
let status = serverStatus.create('name');
|
||||
it('exposes plugins by its id/name', function () {
|
||||
let status = serverStatus.create(plugin);
|
||||
expect(serverStatus.get('name')).to.be(status);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getState(name)', function () {
|
||||
it('should expose the state of the plugin by name', function () {
|
||||
let status = serverStatus.create('name');
|
||||
let status = serverStatus.create(plugin);
|
||||
status.green();
|
||||
expect(serverStatus.getState('name')).to.be('green');
|
||||
});
|
||||
|
@ -39,7 +41,7 @@ describe('ServerStatus class', function () {
|
|||
|
||||
describe('#overall()', function () {
|
||||
it('considers each status to produce a summary', function () {
|
||||
let status = serverStatus.create('name');
|
||||
let status = serverStatus.create(plugin);
|
||||
|
||||
expect(serverStatus.overall().state).to.be('uninitialized');
|
||||
|
||||
|
@ -65,9 +67,13 @@ describe('ServerStatus class', function () {
|
|||
|
||||
describe('#toJSON()', function () {
|
||||
it('serializes to overall status and individuals', function () {
|
||||
let one = serverStatus.create('one');
|
||||
let two = serverStatus.create('two');
|
||||
let three = serverStatus.create('three');
|
||||
const pluginOne = {id: 'one', version: '1.0.0'};
|
||||
const pluginTwo = {id: 'two', version: '2.0.0'};
|
||||
const pluginThree = {id: 'three', version: '3.0.0'};
|
||||
|
||||
let one = serverStatus.create(pluginOne);
|
||||
let two = serverStatus.create(pluginTwo);
|
||||
let three = serverStatus.create(pluginThree);
|
||||
|
||||
one.green();
|
||||
two.yellow();
|
||||
|
|
|
@ -4,6 +4,8 @@ import Status from '../status';
|
|||
import ServerStatus from '../server_status';
|
||||
|
||||
describe('Status class', function () {
|
||||
const plugin = {id: 'test', version: '1.2.3'};
|
||||
|
||||
let server;
|
||||
let serverStatus;
|
||||
|
||||
|
@ -13,11 +15,11 @@ describe('Status class', function () {
|
|||
});
|
||||
|
||||
it('should have an "uninitialized" state initially', function () {
|
||||
expect(serverStatus.create('test')).to.have.property('state', 'uninitialized');
|
||||
expect(serverStatus.create(plugin)).to.have.property('state', 'uninitialized');
|
||||
});
|
||||
|
||||
it('emits change when the status is set', function (done) {
|
||||
let status = serverStatus.create('test');
|
||||
let status = serverStatus.create(plugin);
|
||||
|
||||
status.once('change', function (prev, prevMsg) {
|
||||
expect(status.state).to.be('green');
|
||||
|
@ -40,7 +42,7 @@ describe('Status class', function () {
|
|||
});
|
||||
|
||||
it('should only trigger the change listener when something changes', function () {
|
||||
let status = serverStatus.create('test');
|
||||
let status = serverStatus.create(plugin);
|
||||
let stub = sinon.stub();
|
||||
status.on('change', stub);
|
||||
status.green('Ready');
|
||||
|
@ -50,16 +52,18 @@ describe('Status class', function () {
|
|||
});
|
||||
|
||||
it('should create a JSON representation of the status', function () {
|
||||
let status = serverStatus.create('test');
|
||||
let status = serverStatus.create(plugin);
|
||||
status.green('Ready');
|
||||
|
||||
let json = status.toJSON();
|
||||
expect(json.name).to.eql(plugin.id);
|
||||
expect(json.version).to.eql(plugin.version);
|
||||
expect(json.state).to.eql('green');
|
||||
expect(json.message).to.eql('Ready');
|
||||
});
|
||||
|
||||
it('should call on handler if status is already matched', function (done) {
|
||||
let status = serverStatus.create('test');
|
||||
let status = serverStatus.create(plugin);
|
||||
let msg = 'Test Ready';
|
||||
status.green(msg);
|
||||
|
||||
|
@ -73,7 +77,7 @@ describe('Status class', function () {
|
|||
});
|
||||
|
||||
it('should call once handler if status is already matched', function (done) {
|
||||
let status = serverStatus.create('test');
|
||||
let status = serverStatus.create(plugin);
|
||||
let msg = 'Test Ready';
|
||||
status.green(msg);
|
||||
|
||||
|
@ -88,7 +92,7 @@ describe('Status class', function () {
|
|||
|
||||
function testState(color) {
|
||||
it(`should change the state to ${color} when #${color}() is called`, function () {
|
||||
let status = serverStatus.create('test');
|
||||
let status = serverStatus.create(plugin);
|
||||
let message = 'testing ' + color;
|
||||
status[color](message);
|
||||
expect(status).to.have.property('state', color);
|
||||
|
@ -96,7 +100,7 @@ describe('Status class', function () {
|
|||
});
|
||||
|
||||
it(`should trigger the "change" listner when #${color}() is called`, function (done) {
|
||||
let status = serverStatus.create('test');
|
||||
let status = serverStatus.create(plugin);
|
||||
let message = 'testing ' + color;
|
||||
status.on('change', function (prev, prevMsg) {
|
||||
expect(status.state).to.be(color);
|
||||
|
@ -110,7 +114,7 @@ describe('Status class', function () {
|
|||
});
|
||||
|
||||
it(`should trigger the "${color}" listner when #${color}() is called`, function (done) {
|
||||
let status = serverStatus.create('test');
|
||||
let status = serverStatus.create(plugin);
|
||||
let message = 'testing ' + color;
|
||||
status.on(color, function (prev, prevMsg) {
|
||||
expect(status.state).to.be(color);
|
||||
|
|
|
@ -6,7 +6,7 @@ import { join } from 'path';
|
|||
export default function (kbnServer, server, config) {
|
||||
kbnServer.status = new ServerStatus(kbnServer.server);
|
||||
|
||||
if (server.plugins.good) {
|
||||
if (server.plugins['even-better']) {
|
||||
kbnServer.mixin(require('./metrics'));
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ module.exports = function (kbnServer, server, config) {
|
|||
|
||||
kbnServer.metrics = new Samples(12);
|
||||
|
||||
server.plugins.good.monitor.on('ops', function (event) {
|
||||
server.plugins['even-better'].monitor.on('ops', function (event) {
|
||||
let now = Date.now();
|
||||
let secSinceLast = (now - lastReport) / 1000;
|
||||
lastReport = now;
|
||||
|
|
|
@ -9,8 +9,8 @@ module.exports = class ServerStatus {
|
|||
this._created = {};
|
||||
}
|
||||
|
||||
create(name) {
|
||||
return (this._created[name] = new Status(name, this.server));
|
||||
create(plugin) {
|
||||
return (this._created[plugin.id] = new Status(plugin, this.server));
|
||||
}
|
||||
|
||||
each(fn) {
|
||||
|
|
|
@ -3,22 +3,21 @@ import states from './states';
|
|||
import { EventEmitter } from 'events';
|
||||
|
||||
class Status extends EventEmitter {
|
||||
constructor(name, server) {
|
||||
constructor(plugin, server) {
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
this.plugin = plugin;
|
||||
this.since = new Date();
|
||||
this.state = 'uninitialized';
|
||||
this.message = 'uninitialized';
|
||||
|
||||
this.on('change', function (previous, previousMsg) {
|
||||
this.since = new Date();
|
||||
let tags = ['status', name];
|
||||
let tags = ['status', `plugin:${this.plugin.toString()}`];
|
||||
tags.push(this.state === 'red' ? 'error' : 'info');
|
||||
|
||||
server.log(tags, {
|
||||
tmpl: 'Status changed from <%= prevState %> to <%= state %><%= message ? " - " + message : "" %>',
|
||||
name: name,
|
||||
state: this.state,
|
||||
message: this.message,
|
||||
prevState: previous,
|
||||
|
@ -29,7 +28,8 @@ class Status extends EventEmitter {
|
|||
|
||||
toJSON() {
|
||||
return {
|
||||
name: this.name,
|
||||
name: this.plugin.id,
|
||||
version: this.plugin.version,
|
||||
state: this.state,
|
||||
icon: states.get(this.state).icon,
|
||||
message: this.message,
|
||||
|
|
|
@ -34,8 +34,8 @@ export default function TileMapConverterFn(Private, timefilter, $compile, $rootS
|
|||
properties: {
|
||||
min: _.min(values),
|
||||
max: _.max(values),
|
||||
zoom: _.get(geoAgg, 'params.mapZoom'),
|
||||
center: _.get(geoAgg, 'params.mapCenter')
|
||||
zoom: geoAgg && geoAgg.vis.uiStateVal('mapZoom'),
|
||||
center: geoAgg && geoAgg.vis.uiStateVal('mapCenter')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,6 +6,30 @@ export default function GeoHashAggDefinition(Private, config) {
|
|||
let BucketAggType = Private(AggTypesBucketsBucketAggTypeProvider);
|
||||
let defaultPrecision = 2;
|
||||
|
||||
// zoomPrecision maps event.zoom to a geohash precision value
|
||||
// event.limit is the configurable max geohash precision
|
||||
// default max precision is 7, configurable up to 12
|
||||
const zoomPrecision = {
|
||||
1: 2,
|
||||
2: 2,
|
||||
3: 2,
|
||||
4: 3,
|
||||
5: 3,
|
||||
6: 4,
|
||||
7: 4,
|
||||
8: 5,
|
||||
9: 5,
|
||||
10: 6,
|
||||
11: 6,
|
||||
12: 7,
|
||||
13: 7,
|
||||
14: 8,
|
||||
15: 9,
|
||||
16: 10,
|
||||
17: 11,
|
||||
18: 12
|
||||
};
|
||||
|
||||
function getPrecision(precision) {
|
||||
let maxPrecision = _.parseInt(config.get('visualization:tileMap:maxPrecision'));
|
||||
|
||||
|
@ -45,19 +69,15 @@ export default function GeoHashAggDefinition(Private, config) {
|
|||
},
|
||||
{
|
||||
name: 'precision',
|
||||
default: defaultPrecision,
|
||||
editor: precisionTemplate,
|
||||
controller: function ($scope) {
|
||||
$scope.$watchMulti([
|
||||
'agg.params.autoPrecision',
|
||||
'outputAgg.params.precision'
|
||||
], function (cur, prev) {
|
||||
if (cur[1]) $scope.agg.params.precision = cur[1];
|
||||
});
|
||||
},
|
||||
deserialize: getPrecision,
|
||||
controller: function ($scope) {
|
||||
},
|
||||
write: function (aggConfig, output) {
|
||||
output.params.precision = getPrecision(aggConfig.params.precision);
|
||||
const vis = aggConfig.vis;
|
||||
const currZoom = vis.hasUiState() && vis.uiStateVal('mapZoom');
|
||||
const autoPrecisionVal = zoomPrecision[(currZoom || vis.params.mapZoom)];
|
||||
output.params.precision = aggConfig.params.autoPrecision ? autoPrecisionVal : getPrecision(aggConfig.params.precision);
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
13
src/ui/public/dragula/gu-dragula.less
Normal file
13
src/ui/public/dragula/gu-dragula.less
Normal file
|
@ -0,0 +1,13 @@
|
|||
.gu-handle {
|
||||
cursor: move;
|
||||
cursor: grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: -webkit-grab;
|
||||
}
|
||||
|
||||
.gu-mirror,
|
||||
.gu-mirror .gu-handle {
|
||||
cursor: grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
|
@ -607,3 +607,5 @@ fieldset {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import (reference) "~dragula/dist/dragula.css";
|
||||
|
|
|
@ -29,7 +29,7 @@ paginate {
|
|||
|
||||
a {
|
||||
text-decoration: none;
|
||||
background-color: @white;
|
||||
background-color: @kibanaGray6;
|
||||
margin-left: 2px;
|
||||
padding: 8px 11px;
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ paginate {
|
|||
text-decoration: none !important;
|
||||
font-weight: bold;
|
||||
color: @paginate-page-link-active-color;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
font-size: 1.5em;
|
||||
padding: 5px 0 6px 0;
|
||||
margin: 0 10px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
// Active, hover state for the getTabs
|
||||
> .active > a,
|
||||
|
|
|
@ -58,7 +58,7 @@ describe('Vis Class', function () {
|
|||
|
||||
describe('getState()', function () {
|
||||
it('should get a state that represents the... er... state', function () {
|
||||
let state = vis.getState();
|
||||
let state = vis.getEnabledState();
|
||||
expect(state).to.have.property('type', 'pie');
|
||||
|
||||
expect(state).to.have.property('params');
|
||||
|
|
|
@ -9,6 +9,7 @@ export default function AggConfigFactory(Private, fieldTypeFilter) {
|
|||
self.id = String(opts.id || AggConfig.nextId(vis.aggs));
|
||||
self.vis = vis;
|
||||
self._opts = opts = (opts || {});
|
||||
self.enabled = typeof opts.enabled === 'boolean' ? opts.enabled : true;
|
||||
|
||||
// setters
|
||||
self.type = opts.type;
|
||||
|
@ -232,6 +233,7 @@ export default function AggConfigFactory(Private, fieldTypeFilter) {
|
|||
|
||||
return {
|
||||
id: self.id,
|
||||
enabled: self.enabled,
|
||||
type: self.type && self.type.name,
|
||||
schema: self.schema && self.schema.name,
|
||||
params: outParams
|
||||
|
|
|
@ -2,16 +2,18 @@ import _ from 'lodash';
|
|||
import AggTypesIndexProvider from 'ui/agg_types/index';
|
||||
import RegistryVisTypesProvider from 'ui/registry/vis_types';
|
||||
import VisAggConfigsProvider from 'ui/vis/agg_configs';
|
||||
import PersistedStateProvider from 'ui/persisted_state/persisted_state';
|
||||
export default function VisFactory(Notifier, Private) {
|
||||
let aggTypes = Private(AggTypesIndexProvider);
|
||||
let visTypes = Private(RegistryVisTypesProvider);
|
||||
let AggConfigs = Private(VisAggConfigsProvider);
|
||||
const PersistedState = Private(PersistedStateProvider);
|
||||
|
||||
let notify = new Notifier({
|
||||
location: 'Vis'
|
||||
});
|
||||
|
||||
function Vis(indexPattern, state) {
|
||||
function Vis(indexPattern, state, uiState) {
|
||||
state = state || {};
|
||||
|
||||
if (_.isString(state)) {
|
||||
|
@ -24,6 +26,7 @@ export default function VisFactory(Notifier, Private) {
|
|||
|
||||
// http://aphyr.com/data/posts/317/state.gif
|
||||
this.setState(state);
|
||||
this.setUiState(uiState);
|
||||
}
|
||||
|
||||
Vis.convertOldState = function (type, oldState) {
|
||||
|
@ -44,7 +47,7 @@ export default function VisFactory(Notifier, Private) {
|
|||
oldConfigs.forEach(function (oldConfig) {
|
||||
let agg = {
|
||||
schema: schema.name,
|
||||
type: oldConfig.agg,
|
||||
type: oldConfig.agg
|
||||
};
|
||||
|
||||
let aggType = aggTypes.byName[agg.type];
|
||||
|
@ -81,18 +84,27 @@ export default function VisFactory(Notifier, Private) {
|
|||
this.aggs = new AggConfigs(this, state.aggs);
|
||||
};
|
||||
|
||||
Vis.prototype.getState = function () {
|
||||
Vis.prototype.getStateInternal = function (includeDisabled) {
|
||||
return {
|
||||
title: this.title,
|
||||
type: this.type.name,
|
||||
params: this.params,
|
||||
aggs: this.aggs.map(function (agg) {
|
||||
return agg.toJSON();
|
||||
}).filter(Boolean),
|
||||
aggs: this.aggs
|
||||
.filter(agg => includeDisabled || agg.enabled)
|
||||
.map(agg => agg.toJSON())
|
||||
.filter(Boolean),
|
||||
listeners: this.listeners
|
||||
};
|
||||
};
|
||||
|
||||
Vis.prototype.getEnabledState = function () {
|
||||
return this.getStateInternal(false);
|
||||
};
|
||||
|
||||
Vis.prototype.getState = function () {
|
||||
return this.getStateInternal(true);
|
||||
};
|
||||
|
||||
Vis.prototype.createEditableVis = function () {
|
||||
return this._editableVis || (this._editableVis = this.clone());
|
||||
};
|
||||
|
@ -102,7 +114,8 @@ export default function VisFactory(Notifier, Private) {
|
|||
};
|
||||
|
||||
Vis.prototype.clone = function () {
|
||||
return new Vis(this.indexPattern, this.getState());
|
||||
const uiJson = this.hasUiState() ? this.getUiState().toJSON() : {};
|
||||
return new Vis(this.indexPattern, this.getState(), uiJson);
|
||||
};
|
||||
|
||||
Vis.prototype.requesting = function () {
|
||||
|
@ -125,5 +138,26 @@ export default function VisFactory(Notifier, Private) {
|
|||
});
|
||||
};
|
||||
|
||||
Vis.prototype.hasUiState = function () {
|
||||
return !!this.__uiState;
|
||||
};
|
||||
Vis.prototype.setUiState = function (uiState) {
|
||||
if (uiState instanceof PersistedState) {
|
||||
this.__uiState = uiState;
|
||||
}
|
||||
};
|
||||
Vis.prototype.getUiState = function () {
|
||||
return this.__uiState;
|
||||
};
|
||||
Vis.prototype.uiStateVal = function (key, val) {
|
||||
if (this.hasUiState()) {
|
||||
if (_.isUndefined(val)) {
|
||||
return this.__uiState.get(key);
|
||||
}
|
||||
return this.__uiState.set(key, val);
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
return Vis;
|
||||
};
|
||||
|
|
|
@ -81,6 +81,7 @@ describe('TileMap Tests', function () {
|
|||
|
||||
it('should only add controls if data exists', function () {
|
||||
let noData = {
|
||||
geohashGridAgg: { vis: { params: {} } },
|
||||
geoJson: {
|
||||
features: [],
|
||||
properties: {},
|
||||
|
|
|
@ -55,7 +55,7 @@ export default function HandlerBaseClass(Private) {
|
|||
this.getProxyHandler = _.memoize(function (event) {
|
||||
let self = this;
|
||||
return function (e) {
|
||||
self.vis.emit(event, e);
|
||||
self.vis.emit(event, e, vis.uiState);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ export default function MapFactory(Private) {
|
|||
this._valueFormatter = params.valueFormatter || _.identity;
|
||||
this._tooltipFormatter = params.tooltipFormatter || _.identity;
|
||||
this._geoJson = _.get(this._chartData, 'geoJson');
|
||||
this._mapZoom = params.zoom || defaultMapZoom;
|
||||
this._mapCenter = params.center || defaultMapCenter;
|
||||
this._attr = params.attr || {};
|
||||
|
||||
let mapOptions = {
|
||||
|
@ -211,7 +213,8 @@ export default function MapFactory(Private) {
|
|||
this.map.on('moveend', function setZoomCenter(ev) {
|
||||
if (!self.map) return;
|
||||
// update internal center and zoom references
|
||||
self._mapCenter = self.map.getCenter();
|
||||
const uglyCenter = self.map.getCenter();
|
||||
self._mapCenter = [uglyCenter.lat, uglyCenter.lng];
|
||||
self._mapZoom = self.map.getZoom();
|
||||
self._addMarkers();
|
||||
|
||||
|
@ -272,10 +275,6 @@ export default function MapFactory(Private) {
|
|||
TileMapMap.prototype._createMap = function (mapOptions) {
|
||||
if (this.map) this.destroy();
|
||||
|
||||
// get center and zoom from mapdata, or use defaults
|
||||
this._mapCenter = _.get(this._geoJson, 'properties.center') || defaultMapCenter;
|
||||
this._mapZoom = _.get(this._geoJson, 'properties.zoom') || defaultMapZoom;
|
||||
|
||||
// add map tiles layer, using the mapTiles object settings
|
||||
if (this._attr.wms && this._attr.wms.enabled) {
|
||||
this._tileLayer = L.tileLayer.wms(this._attr.wms.url, this._attr.wms.options);
|
||||
|
|
|
@ -98,11 +98,17 @@ export default function TileMapFactory(Private) {
|
|||
* @param selection {Object} d3 selection
|
||||
*/
|
||||
TileMap.prototype._appendMap = function (selection) {
|
||||
let container = $(selection).addClass('tilemap');
|
||||
const container = $(selection).addClass('tilemap');
|
||||
const uiStateParams = this.handler.vis ? {
|
||||
mapCenter: this.handler.vis.uiState.get('mapCenter'),
|
||||
mapZoom: this.handler.vis.uiState.get('mapZoom')
|
||||
} : {};
|
||||
|
||||
let map = new TileMapMap(container, this._chartData, {
|
||||
// center: this._attr.mapCenter,
|
||||
// zoom: this._attr.mapZoom,
|
||||
const params = _.assign({}, _.get(this._chartData, 'geoAgg.vis.params'), uiStateParams);
|
||||
|
||||
const map = new TileMapMap(container, this._chartData, {
|
||||
center: params.mapCenter,
|
||||
zoom: params.mapZoom,
|
||||
events: this.events,
|
||||
markerType: this._attr.mapType,
|
||||
tooltipFormatter: this.tooltipFormatter,
|
||||
|
|
|
@ -29,8 +29,7 @@ uiModules
|
|||
},
|
||||
template: visualizeTemplate,
|
||||
link: function ($scope, $el, attr) {
|
||||
let chart; // set in "vis" watcher
|
||||
let minVisChartHeight = 180;
|
||||
const minVisChartHeight = 180;
|
||||
|
||||
if (_.isUndefined($scope.showSpyPanel)) {
|
||||
$scope.showSpyPanel = true;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { defaultsDeep } from 'lodash';
|
||||
import { defaultsDeep, partial } from 'lodash';
|
||||
import defaultsProvider from './defaults';
|
||||
|
||||
export default function setupSettings(kbnServer, server, config) {
|
||||
|
@ -23,12 +23,19 @@ export default function setupSettings(kbnServer, server, config) {
|
|||
return Promise.resolve(defaultsProvider());
|
||||
}
|
||||
|
||||
function userSettingsNotFound(kibanaVersion) {
|
||||
const message = 'Could not find user-provided settings for this version of Kibana (' + kibanaVersion + ')';
|
||||
server.plugins.kibana.status.red(message);
|
||||
return {};
|
||||
}
|
||||
|
||||
function getUserProvided() {
|
||||
const { client } = server.plugins.elasticsearch;
|
||||
const clientSettings = getClientSettings(config);
|
||||
return client
|
||||
.get({ ...clientSettings })
|
||||
.then(res => res._source)
|
||||
.catch(partial(userSettingsNotFound, clientSettings.id))
|
||||
.then(user => hydrateUserSettings(user));
|
||||
}
|
||||
|
||||
|
|
8
test/fixtures/dump_data/dashboard.data.json
vendored
Normal file
8
test/fixtures/dump_data/dashboard.data.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
test/fixtures/dump_data/dashboard.mapping.json
vendored
Normal file
1
test/fixtures/dump_data/dashboard.mapping.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{".kibana":{"mappings":{"config":{"properties":{"buildNum":{"type":"keyword"}}},"index-pattern":{"properties":{"fields":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"timeFieldName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"title":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}},"search":{"properties":{"columns":{"type":"text"},"description":{"type":"text"},"hits":{"type":"integer"},"kibanaSavedObjectMeta":{"properties":{"searchSourceJSON":{"type":"text"}}},"sort":{"type":"text"},"title":{"type":"text"},"version":{"type":"integer"}}},"visualization":{"properties":{"description":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"kibanaSavedObjectMeta":{"properties":{"searchSourceJSON":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}},"title":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"uiStateJSON":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"version":{"type":"integer"},"visState":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}},"server":{"properties":{"uuid":{"type":"keyword"}}},"dashboard":{"properties":{"description":{"type":"text"},"hits":{"type":"integer"},"kibanaSavedObjectMeta":{"properties":{"searchSourceJSON":{"type":"text"}}},"optionsJSON":{"type":"text"},"panelsJSON":{"type":"text"},"timeFrom":{"type":"text"},"timeRestore":{"type":"boolean"},"timeTo":{"type":"text"},"title":{"type":"text"},"uiStateJSON":{"type":"text"},"version":{"type":"integer"}}}}}}
|
1
test/fixtures/dump_data/visualize.data.json
vendored
Normal file
1
test/fixtures/dump_data/visualize.data.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
test/fixtures/dump_data/visualize.mapping.json
vendored
Normal file
1
test/fixtures/dump_data/visualize.mapping.json
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
{".kibana":{"mappings":{"index-pattern":{"properties":{"fields":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"timeFieldName":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"title":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}}}}
|
|
@ -2,8 +2,6 @@ import {
|
|||
bdd,
|
||||
scenarioManager,
|
||||
common,
|
||||
// settingsPage,
|
||||
// headerPage,
|
||||
consolePage
|
||||
} from '../../../support';
|
||||
|
||||
|
|
|
@ -8,10 +8,6 @@ import { bdd, remote, scenarioManager, defaultTimeout } from '../../../support';
|
|||
return remote.setWindowSize(1200,800);
|
||||
});
|
||||
|
||||
bdd.after(function unloadMakelogs() {
|
||||
return scenarioManager.unload('logstashFunctional');
|
||||
});
|
||||
|
||||
require('./_console');
|
||||
});
|
||||
}());
|
||||
|
|
139
test/functional/apps/dashboard/_dashboard.js
Normal file
139
test/functional/apps/dashboard/_dashboard.js
Normal file
|
@ -0,0 +1,139 @@
|
|||
import {
|
||||
bdd,
|
||||
common,
|
||||
dashboardPage,
|
||||
headerPage,
|
||||
scenarioManager,
|
||||
esClient,
|
||||
elasticDump
|
||||
} from '../../../support';
|
||||
|
||||
(function () {
|
||||
var expect = require('expect.js');
|
||||
|
||||
(function () {
|
||||
bdd.describe('dashboard tab', function describeIndexTests() {
|
||||
|
||||
bdd.before(function () {
|
||||
|
||||
common.debug('Starting dashboard before method');
|
||||
var logstash = scenarioManager.loadIfEmpty('logstashFunctional');
|
||||
return esClient.delete('.kibana')
|
||||
.then(function () {
|
||||
return common.try(function () {
|
||||
return esClient.updateConfigDoc({'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*'});
|
||||
});
|
||||
})
|
||||
// and load a set of makelogs data
|
||||
.then(function loadkibanaVisualizations() {
|
||||
common.debug('load kibana index with visualizations');
|
||||
return elasticDump.elasticLoad('dashboard','.kibana');
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('navigateToApp dashboard');
|
||||
return common.navigateToApp('dashboard');
|
||||
})
|
||||
// wait for the logstash data load to finish if it hasn't already
|
||||
.then(function () {
|
||||
return logstash;
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
|
||||
bdd.describe('add visualizations to dashboard', function dashboardTest() {
|
||||
var visualizations = ['Visualization漢字 AreaChart',
|
||||
'Visualization☺漢字 DataTable',
|
||||
'Visualization漢字 LineChart',
|
||||
'Visualization PieChart',
|
||||
'Visualization TileMap',
|
||||
'Visualization☺ VerticalBarChart',
|
||||
'Visualization MetricChart'
|
||||
];
|
||||
|
||||
|
||||
bdd.it('should be able to add visualizations to dashboard', function addVisualizations() {
|
||||
|
||||
function addVisualizations(arr) {
|
||||
return arr.reduce(function (promise, vizName) {
|
||||
return promise
|
||||
.then(function () {
|
||||
return dashboardPage.addVisualization(vizName);
|
||||
});
|
||||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
return addVisualizations(visualizations)
|
||||
.then(function () {
|
||||
common.debug('done adding visualizations');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
bdd.it('set the timepicker time to that which contains our test data', function setTimepicker() {
|
||||
var fromTime = '2015-09-19 06:31:44.000';
|
||||
var toTime = '2015-09-23 18:31:44.000';
|
||||
var testSubName = 'Dashboard Test 1';
|
||||
|
||||
// .then(function () {
|
||||
common.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"');
|
||||
return headerPage.setAbsoluteRange(fromTime, toTime)
|
||||
.then(function sleep() {
|
||||
return common.sleep(4000);
|
||||
})
|
||||
.then(function takeScreenshot() {
|
||||
common.debug('Take screenshot');
|
||||
common.saveScreenshot('./screenshot-' + testSubName + '.png');
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should save and load dashboard', function saveAndLoadDashboard() {
|
||||
var testSubName = 'Dashboard Test 1';
|
||||
// TODO: save time on the dashboard and test it
|
||||
return dashboardPage.saveDashboard(testSubName)
|
||||
// click New Dashboard just to clear the one we just created
|
||||
.then(function () {
|
||||
return dashboardPage.clickNewDashboard();
|
||||
})
|
||||
.then(function () {
|
||||
return dashboardPage.loadSavedDashboard(testSubName);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should have all the expected visualizations', function checkVisualizations() {
|
||||
return common.tryForTime(10000, function () {
|
||||
return dashboardPage.getPanelTitles()
|
||||
.then(function (panelTitles) {
|
||||
common.log('visualization titles = ' + panelTitles);
|
||||
expect(panelTitles).to.eql(visualizations);
|
||||
});
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should have all the expected initial sizes', function checkVisualizationSizes() {
|
||||
var visObjects = [ { dataCol: '1', dataRow: '1', dataSizeX: '3', dataSizeY: '2', title: 'Visualization漢字 AreaChart' },
|
||||
{ dataCol: '4', dataRow: '1', dataSizeX: '3', dataSizeY: '2', title: 'Visualization☺漢字 DataTable' },
|
||||
{ dataCol: '7', dataRow: '1', dataSizeX: '3', dataSizeY: '2', title: 'Visualization漢字 LineChart' },
|
||||
{ dataCol: '10', dataRow: '1', dataSizeX: '3', dataSizeY: '2', title: 'Visualization PieChart' },
|
||||
{ dataCol: '1', dataRow: '3', dataSizeX: '3', dataSizeY: '2', title: 'Visualization TileMap' },
|
||||
{ dataCol: '4', dataRow: '3', dataSizeX: '3', dataSizeY: '2', title: 'Visualization☺ VerticalBarChart' },
|
||||
{ dataCol: '7', dataRow: '3', dataSizeX: '3', dataSizeY: '2', title: 'Visualization MetricChart' }
|
||||
];
|
||||
return common.tryForTime(10000, function () {
|
||||
return dashboardPage.getPanelData()
|
||||
.then(function (panelTitles) {
|
||||
common.log('visualization titles = ' + panelTitles);
|
||||
expect(panelTitles).to.eql(visObjects);
|
||||
});
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}());
|
||||
}());
|
13
test/functional/apps/dashboard/index.js
Normal file
13
test/functional/apps/dashboard/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { bdd, remote, scenarioManager, defaultTimeout } from '../../../support';
|
||||
|
||||
(function () {
|
||||
bdd.describe('dashboard app', function () {
|
||||
this.timeout = defaultTimeout;
|
||||
|
||||
bdd.before(function () {
|
||||
return remote.setWindowSize(1200,800);
|
||||
});
|
||||
|
||||
require('./_dashboard');
|
||||
});
|
||||
}());
|
|
@ -152,7 +152,7 @@ import {
|
|||
];
|
||||
return discoverPage.setChartInterval(chartInterval)
|
||||
.then(function () {
|
||||
return common.sleep(8000);
|
||||
return common.sleep(4000);
|
||||
})
|
||||
.then(function () {
|
||||
return verifyChartData(expectedBarChartData);
|
||||
|
@ -167,7 +167,7 @@ import {
|
|||
];
|
||||
return discoverPage.setChartInterval(chartInterval)
|
||||
.then(function () {
|
||||
return common.sleep(8000);
|
||||
return common.sleep(4000);
|
||||
})
|
||||
.then(function () {
|
||||
return verifyChartData(expectedBarChartData);
|
||||
|
@ -188,6 +188,26 @@ import {
|
|||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('browser back button should show previous interval Daily', function () {
|
||||
var expectedChartInterval = 'Daily';
|
||||
var expectedBarChartData = [
|
||||
'133.196', '129.192', '129.724'
|
||||
];
|
||||
return this.remote.goBack()
|
||||
.then(function () {
|
||||
return common.try(function tryingForTime() {
|
||||
return discoverPage.getChartInterval()
|
||||
.then(function (actualInterval) {
|
||||
expect(actualInterval).to.be(expectedChartInterval);
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
return verifyChartData(expectedBarChartData);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should show correct data for chart interval Monthly', function () {
|
||||
var chartInterval = 'Monthly';
|
||||
var expectedBarChartData = [ '122.535'];
|
||||
|
@ -241,6 +261,12 @@ import {
|
|||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should not show "no results"', () => {
|
||||
return discoverPage.hasNoResults().then(visible => {
|
||||
expect(visible).to.be(false);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
function verifyChartData(expectedBarChartData) {
|
||||
return common.try(function tryingForTime() {
|
||||
|
@ -270,6 +296,69 @@ import {
|
|||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
bdd.describe('query #2, which has an empty time range', function () {
|
||||
var fromTime = '1999-06-11 09:22:11.000';
|
||||
var toTime = '1999-06-12 11:21:04.000';
|
||||
|
||||
bdd.before(() => {
|
||||
common.debug('setAbsoluteRangeForAnotherQuery');
|
||||
return headerPage
|
||||
.setAbsoluteRange(fromTime, toTime)
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should show "no results"', () => {
|
||||
return discoverPage.hasNoResults().then(visible => {
|
||||
expect(visible).to.be(true);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should suggest a new time range is picked', () => {
|
||||
return discoverPage.hasNoResultsTimepicker().then(visible => {
|
||||
expect(visible).to.be(true);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should open and close the time picker', () => {
|
||||
let i = 0;
|
||||
|
||||
return closeTimepicker() // close
|
||||
.then(() => isTimepickerOpen(false)
|
||||
.then(el => el.click()) // open
|
||||
.then(() => isTimepickerOpen(true))
|
||||
.then(el => el.click()) // close
|
||||
.then(() => isTimepickerOpen(false))
|
||||
.catch(common.handleError(this))
|
||||
);
|
||||
|
||||
function closeTimepicker() {
|
||||
return headerPage.isTimepickerOpen().then(shown => {
|
||||
if (!shown) {
|
||||
return;
|
||||
}
|
||||
return discoverPage
|
||||
.getNoResultsTimepicker()
|
||||
.click(); // close
|
||||
});
|
||||
}
|
||||
|
||||
function isTimepickerOpen(expected) {
|
||||
return headerPage.isTimepickerOpen().then(shown => {
|
||||
common.debug(`expect (#${++i}) timepicker to be ${peek(expected)} (is ${peek(shown)}).`);
|
||||
expect(shown).to.be(expected);
|
||||
return discoverPage.getNoResultsTimepicker();
|
||||
function peek(state) {
|
||||
return state ? 'open' : 'closed';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}());
|
||||
}());
|
||||
|
|
|
@ -16,29 +16,8 @@ import {
|
|||
var fromTime = '2015-09-19 06:31:44.000';
|
||||
var toTime = '2015-09-23 18:31:44.000';
|
||||
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
common.debug('navigateTo');
|
||||
return settingsPage.navigateTo().then(settingsPage.clickExistingIndicesAddDataLink);
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('createIndexPattern');
|
||||
return settingsPage.createIndexPattern();
|
||||
})
|
||||
.then(function () {
|
||||
return settingsPage.clickAdvancedTab();
|
||||
})
|
||||
.then(function GetAdvancedSetting() {
|
||||
common.debug('check for required UTC timezone');
|
||||
return settingsPage.getAdvancedSettings('dateFormat:tz');
|
||||
})
|
||||
.then(function (advancedSetting) {
|
||||
expect(advancedSetting).to.be('UTC');
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize');
|
||||
})
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize')
|
||||
.then(function () {
|
||||
common.debug('clickAreaChart');
|
||||
return visualizePage.clickAreaChart();
|
||||
|
|
|
@ -11,23 +11,11 @@ import {
|
|||
|
||||
(function () {
|
||||
bdd.describe('visualize app', function describeIndexTests() {
|
||||
bdd.before(function () {
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
common.debug('navigateTo');
|
||||
return settingsPage.navigateTo().then(settingsPage.clickExistingIndicesAddDataLink);
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('createIndexPattern');
|
||||
return settingsPage.createIndexPattern();
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize');
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.before(function () {
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize');
|
||||
});
|
||||
|
||||
bdd.describe('chart types', function indexPatternCreation() {
|
||||
|
||||
|
|
|
@ -16,29 +16,8 @@ import {
|
|||
var toTime = '2015-09-23 18:31:44.000';
|
||||
|
||||
bdd.before(function () {
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
common.debug('navigateTo');
|
||||
return settingsPage.navigateTo().then(settingsPage.clickExistingIndicesAddDataLink);
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('createIndexPattern');
|
||||
return settingsPage.createIndexPattern();
|
||||
})
|
||||
.then(function () {
|
||||
return settingsPage.clickAdvancedTab();
|
||||
})
|
||||
.then(function GetAdvancedSetting() {
|
||||
common.debug('check for required UTC timezone');
|
||||
return settingsPage.getAdvancedSettings('dateFormat:tz');
|
||||
})
|
||||
.then(function (advancedSetting) {
|
||||
expect(advancedSetting).to.be('UTC');
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize');
|
||||
})
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize')
|
||||
.then(function () {
|
||||
common.debug('clickDataTable');
|
||||
return visualizePage.clickDataTable();
|
||||
|
|
|
@ -16,29 +16,8 @@ import {
|
|||
var fromTime = '2015-09-19 06:31:44.000';
|
||||
var toTime = '2015-09-23 18:31:44.000';
|
||||
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
common.debug('navigateTo');
|
||||
return settingsPage.navigateTo().then(settingsPage.clickExistingIndicesAddDataLink);
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('createIndexPattern');
|
||||
return settingsPage.createIndexPattern();
|
||||
})
|
||||
.then(function () {
|
||||
return settingsPage.clickAdvancedTab();
|
||||
})
|
||||
.then(function GetAdvancedSetting() {
|
||||
common.debug('check for required UTC timezone');
|
||||
return settingsPage.getAdvancedSettings('dateFormat:tz');
|
||||
})
|
||||
.then(function (advancedSetting) {
|
||||
expect(advancedSetting).to.be('UTC');
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize');
|
||||
})
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize')
|
||||
.then(function () {
|
||||
common.debug('clickLineChart');
|
||||
return visualizePage.clickLineChart();
|
||||
|
|
|
@ -21,29 +21,8 @@ import {
|
|||
common.debug('Start of test' + testSubName + 'Visualization');
|
||||
var vizName1 = 'Visualization ' + testSubName;
|
||||
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
common.debug('navigateTo');
|
||||
return settingsPage.navigateTo().then(settingsPage.clickExistingIndicesAddDataLink);
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('createIndexPattern');
|
||||
return settingsPage.createIndexPattern();
|
||||
})
|
||||
.then(function () {
|
||||
return settingsPage.clickAdvancedTab();
|
||||
})
|
||||
.then(function GetAdvancedSetting() {
|
||||
common.debug('check for required UTC timezone');
|
||||
return settingsPage.getAdvancedSettings('dateFormat:tz');
|
||||
})
|
||||
.then(function (advancedSetting) {
|
||||
expect(advancedSetting).to.be('UTC');
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize');
|
||||
})
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize')
|
||||
.then(function () {
|
||||
common.debug('clickMetric');
|
||||
return visualizePage.clickMetric();
|
||||
|
|
|
@ -16,29 +16,8 @@ import {
|
|||
var fromTime = '2015-09-19 06:31:44.000';
|
||||
var toTime = '2015-09-23 18:31:44.000';
|
||||
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
common.debug('navigateTo');
|
||||
return settingsPage.navigateTo().then(settingsPage.clickExistingIndicesAddDataLink);
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('createIndexPattern');
|
||||
return settingsPage.createIndexPattern();
|
||||
})
|
||||
.then(function () {
|
||||
return settingsPage.clickAdvancedTab();
|
||||
})
|
||||
.then(function GetAdvancedSetting() {
|
||||
common.debug('check for required UTC timezone');
|
||||
return settingsPage.getAdvancedSettings('dateFormat:tz');
|
||||
})
|
||||
.then(function (advancedSetting) {
|
||||
expect(advancedSetting).to.be('UTC');
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize');
|
||||
})
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize')
|
||||
.then(function () {
|
||||
common.debug('clickPieChart');
|
||||
return visualizePage.clickPieChart();
|
||||
|
|
|
@ -17,29 +17,8 @@ import {
|
|||
|
||||
bdd.before(function () {
|
||||
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
common.debug('navigateTo');
|
||||
return settingsPage.navigateTo().then(settingsPage.clickExistingIndicesAddDataLink);
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('createIndexPattern');
|
||||
return settingsPage.createIndexPattern();
|
||||
})
|
||||
.then(function () {
|
||||
return settingsPage.clickAdvancedTab();
|
||||
})
|
||||
.then(function GetAdvancedSetting() {
|
||||
common.debug('check for required UTC timezone');
|
||||
return settingsPage.getAdvancedSettings('dateFormat:tz');
|
||||
})
|
||||
.then(function (advancedSetting) {
|
||||
expect(advancedSetting).to.be('UTC');
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize');
|
||||
})
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize')
|
||||
.then(function () {
|
||||
common.debug('clickTileMap');
|
||||
return visualizePage.clickTileMap();
|
||||
|
|
|
@ -16,29 +16,8 @@ import {
|
|||
var toTime = '2015-09-23 18:31:44.000';
|
||||
|
||||
bdd.before(function () {
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
common.debug('navigateTo');
|
||||
return settingsPage.navigateTo().then(settingsPage.clickExistingIndicesAddDataLink);
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('createIndexPattern');
|
||||
return settingsPage.createIndexPattern();
|
||||
})
|
||||
.then(function () {
|
||||
return settingsPage.clickAdvancedTab();
|
||||
})
|
||||
.then(function GetAdvancedSetting() {
|
||||
common.debug('check for required UTC timezone');
|
||||
return settingsPage.getAdvancedSettings('dateFormat:tz');
|
||||
})
|
||||
.then(function (advancedSetting) {
|
||||
expect(advancedSetting).to.be('UTC');
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize');
|
||||
})
|
||||
common.debug('navigateToApp visualize');
|
||||
return common.navigateToApp('visualize')
|
||||
.then(function () {
|
||||
common.debug('clickVerticalBarChart');
|
||||
return visualizePage.clickVerticalBarChart();
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import { bdd, remote, common, defaultTimeout, scenarioManager } from '../../../support';
|
||||
import {
|
||||
bdd,
|
||||
remote,
|
||||
common,
|
||||
defaultTimeout,
|
||||
scenarioManager,
|
||||
esClient,
|
||||
elasticDump
|
||||
} from '../../../support';
|
||||
|
||||
(function () {
|
||||
bdd.describe('visualize app', function () {
|
||||
|
@ -7,14 +15,24 @@ import { bdd, remote, common, defaultTimeout, scenarioManager } from '../../../s
|
|||
bdd.before(function () {
|
||||
var self = this;
|
||||
remote.setWindowSize(1200,800);
|
||||
// load a set of makelogs data
|
||||
common.debug('loadIfEmpty logstashFunctional ' + self.timeout);
|
||||
return scenarioManager.loadIfEmpty('logstashFunctional');
|
||||
});
|
||||
|
||||
|
||||
bdd.after(function unloadMakelogs() {
|
||||
return scenarioManager.unload('logstashFunctional');
|
||||
common.debug('Starting visualize before method');
|
||||
var logstash = scenarioManager.loadIfEmpty('logstashFunctional');
|
||||
return esClient.delete('.kibana')
|
||||
.then(function () {
|
||||
return common.try(function () {
|
||||
return esClient.updateConfigDoc({'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*'});
|
||||
});
|
||||
})
|
||||
.then(function loadkibanaIndexPattern() {
|
||||
common.debug('load kibana index with default index pattern');
|
||||
return elasticDump.elasticLoad('visualize','.kibana');
|
||||
})
|
||||
// wait for the logstash data load to finish if it hasn't already
|
||||
.then(function () {
|
||||
return logstash;
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
require('./_chart_types');
|
||||
|
|
|
@ -24,7 +24,8 @@ define(function (require) {
|
|||
'intern/dojo/node!./status_page',
|
||||
'intern/dojo/node!./apps/settings',
|
||||
'intern/dojo/node!./apps/visualize',
|
||||
'intern/dojo/node!./apps/console'
|
||||
'intern/dojo/node!./apps/console',
|
||||
'intern/dojo/node!./apps/dashboard'
|
||||
], function () {});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ import { bdd, common } from '../../support';
|
|||
.findByCssSelector('.plugin_status_breakdown')
|
||||
.getVisibleText()
|
||||
.then(function (text) {
|
||||
expect(text.indexOf('plugin:kibana Ready')).to.be.above(-1);
|
||||
expect(text.indexOf('kibana 1.0.0 Ready')).to.be.above(-1);
|
||||
});
|
||||
})
|
||||
.catch(common.handleError(self));
|
||||
|
|
109
test/support/elastic_dump.js
Normal file
109
test/support/elastic_dump.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { common, config} from './';
|
||||
|
||||
export default (function () {
|
||||
var util = require('util');
|
||||
var path = require('path');
|
||||
var url = require('url');
|
||||
var resolve = require('path').resolve;
|
||||
var Elasticdump = require('elasticdump').elasticdump;
|
||||
|
||||
function ElasticDump() {
|
||||
}
|
||||
|
||||
ElasticDump.prototype = {
|
||||
|
||||
/*
|
||||
** This function is basically copied from
|
||||
** https://github.com/taskrabbit/elasticsearch-dump/blob/master/bin/elasticdump
|
||||
** and allows calling elasticdump for importing or exporting data from Elasticsearch
|
||||
*/
|
||||
elasticdumpModule: function elasticdumpModule(myinput, myoutput, index, mytype) {
|
||||
|
||||
var options = {
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
debug: false,
|
||||
type: mytype,
|
||||
delete: false,
|
||||
all: false,
|
||||
maxSockets: null,
|
||||
input: myinput,
|
||||
'input-index': null,
|
||||
output: myoutput,
|
||||
'output-index': index,
|
||||
inputTransport: null,
|
||||
outputTransport: null,
|
||||
searchBody: null,
|
||||
sourceOnly: false,
|
||||
jsonLines: false,
|
||||
format: '',
|
||||
'ignore-errors': false,
|
||||
scrollTime: '10m',
|
||||
timeout: null,
|
||||
skip: null,
|
||||
toLog: null,
|
||||
};
|
||||
var dumper = new Elasticdump(options.input, options.output, options);
|
||||
|
||||
dumper.on('log', function (message) { common.debug(message); });
|
||||
dumper.on('error', function (error) { common.debug('error', 'Error Emitted => ' + (error.message || JSON.stringify(error))); });
|
||||
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
dumper.dump(function (error, totalWrites) {
|
||||
if (error) {
|
||||
common.debug('THERE WAS AN ERROR :-(');
|
||||
reject(Error(error));
|
||||
} else {
|
||||
resolve ('elasticdumpModule success');
|
||||
}
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
/*
|
||||
** Dumps data from Elasticsearch into json files.
|
||||
** Takes a simple filename as input like 'dashboard' (for dashboard tests).
|
||||
** Appends ''.mapping.json' and '.data.json' for the actual filenames.
|
||||
** Writes files to the Kibana root dir.
|
||||
** Fails if the files already exist, so consider appending a timestamp to filename.
|
||||
*/
|
||||
elasticDump: function elasticDump(index, file) {
|
||||
var self = this;
|
||||
common.debug('Dumping mapping from ' + url.format(config.servers.elasticsearch) + '/' + index
|
||||
+ ' to (' + file + '.mapping.json)');
|
||||
return this.elasticdumpModule(url.format(config.servers.elasticsearch),
|
||||
file + '.mapping.json', index, 'mapping')
|
||||
.then(function () {
|
||||
common.debug('Dumping data from ' + url.format(config.servers.elasticsearch) + '/' + index
|
||||
+ ' to (' + file + '.data.json)');
|
||||
return self.elasticdumpModule(url.format(config.servers.elasticsearch),
|
||||
file + '.data.json', index, 'data');
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
** Loads data from json files into Elasticsearch.
|
||||
** Takes a simple filename as input like 'dashboard' (for dashboard tests).
|
||||
** Appends ''.mapping.json' and '.data.json' for the actual filenames.
|
||||
** Path /test/fixtures/dump_data is hard-coded
|
||||
*/
|
||||
elasticLoad: function elasticLoad(file, index) {
|
||||
// TODO: should we have a flag to delete the index first?
|
||||
// or use scenarioManager.unload(index) ? <<- currently this
|
||||
var self = this;
|
||||
common.debug('Loading mapping (test/fixtures/dump_data/' + file + '.mapping.json) into '
|
||||
+ url.format(config.servers.elasticsearch) + '/' + index);
|
||||
return this.elasticdumpModule('test/fixtures/dump_data/' + file + '.mapping.json',
|
||||
url.format(config.servers.elasticsearch), index, 'mapping')
|
||||
.then(function () {
|
||||
common.debug('Loading data (test/fixtures/dump_data/' + file + '.data.json) into '
|
||||
+ url.format(config.servers.elasticsearch) + '/' + index);
|
||||
return self.elasticdumpModule('test/fixtures/dump_data/' + file + '.data.json',
|
||||
url.format(config.servers.elasticsearch), index, 'data');
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
return ElasticDump;
|
||||
}());
|
96
test/support/es_client.js
Normal file
96
test/support/es_client.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
import { common, remote} from './';
|
||||
|
||||
export default (function () {
|
||||
|
||||
var elasticsearch = require('elasticsearch');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
function EsClient(server) {
|
||||
this.remote = remote;
|
||||
if (!server) throw new Error('No server defined');
|
||||
|
||||
// NOTE: some large sets of test data can take several minutes to load
|
||||
this.client = new elasticsearch.Client({
|
||||
host: server,
|
||||
requestTimeout: 300000,
|
||||
defer: function () {
|
||||
return Promise.defer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
EsClient.prototype = {
|
||||
constructor: EsClient,
|
||||
|
||||
|
||||
/**
|
||||
* Delete an index
|
||||
* @param {string} index
|
||||
* @return {Promise} A promise that is resolved when elasticsearch has a response
|
||||
*/
|
||||
delete: function (index) {
|
||||
|
||||
return this.client.indices.delete({
|
||||
index: index
|
||||
})
|
||||
.catch(function (reason) {
|
||||
// if the index never existed yet, or was already deleted it's OK
|
||||
if (reason.message.indexOf('index_not_found_exception') < 0) {
|
||||
common.debug('reason.message: ' + reason.message);
|
||||
throw reason;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Add fields to the config doc (like setting timezone and defaultIndex)
|
||||
* @return {Promise} A promise that is resolved when elasticsearch has a response
|
||||
*/
|
||||
updateConfigDoc: function (docMap) {
|
||||
// first we need to get the config doc's id so we can use it in our _update call
|
||||
var self = this;
|
||||
var configId;
|
||||
var docMapString = JSON.stringify(docMap);
|
||||
|
||||
return this.client.search({
|
||||
index: '.kibana',
|
||||
type: 'config'
|
||||
})
|
||||
.then(function (response) {
|
||||
if (response.errors) {
|
||||
throw new Error(
|
||||
'get config failed\n' +
|
||||
response.items
|
||||
.map(i => i[Object.keys(i)[0]].error)
|
||||
.filter(Boolean)
|
||||
.map(err => ' ' + JSON.stringify(err))
|
||||
.join('\n')
|
||||
);
|
||||
} else {
|
||||
configId = response.hits.hits[0]._id;
|
||||
common.debug('config._id =' + configId);
|
||||
}
|
||||
})
|
||||
// now that we have the id, we can update
|
||||
// return scenarioManager.updateConfigDoc({'dateFormat:tz':'UTC', 'defaultIndex':'logstash-*'});
|
||||
.then(function (response) {
|
||||
common.debug('updating config with ' + docMapString);
|
||||
return self.client.update({
|
||||
index: '.kibana',
|
||||
type: 'config',
|
||||
id: configId,
|
||||
body: {
|
||||
'doc':
|
||||
docMap
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function (err) {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return EsClient;
|
||||
}());
|
|
@ -1,10 +1,13 @@
|
|||
import url from 'url';
|
||||
import EsClient from './es_client';
|
||||
import ElasticDump from './elastic_dump';
|
||||
import ScenarioManager from '../fixtures/scenario_manager';
|
||||
import Common from './pages/common';
|
||||
import DiscoverPage from './pages/discover_page';
|
||||
import SettingsPage from './pages/settings_page';
|
||||
import HeaderPage from './pages/header_page';
|
||||
import VisualizePage from './pages/visualize_page';
|
||||
import DashboardPage from './pages/dashboard_page';
|
||||
import ShieldPage from './pages/shield_page';
|
||||
import ConsolePage from './pages/console_page';
|
||||
|
||||
|
@ -17,6 +20,7 @@ exports.defaultTimeout = exports.config.defaultTimeout;
|
|||
exports.defaultTryTimeout = exports.config.defaultTryTimeout;
|
||||
exports.defaultFindTimeout = exports.config.defaultFindTimeout;
|
||||
exports.scenarioManager = new ScenarioManager(url.format(exports.config.servers.elasticsearch));
|
||||
exports.esClient = new EsClient(url.format(exports.config.servers.elasticsearch));
|
||||
|
||||
defineDelayedExport('remote', (suite) => suite.remote);
|
||||
defineDelayedExport('common', () => new Common());
|
||||
|
@ -24,8 +28,10 @@ defineDelayedExport('discoverPage', () => new DiscoverPage());
|
|||
defineDelayedExport('headerPage', () => new HeaderPage());
|
||||
defineDelayedExport('settingsPage', () => new SettingsPage());
|
||||
defineDelayedExport('visualizePage', () => new VisualizePage());
|
||||
defineDelayedExport('dashboardPage', () => new DashboardPage());
|
||||
defineDelayedExport('shieldPage', () => new ShieldPage());
|
||||
defineDelayedExport('consolePage', () => new ConsolePage());
|
||||
defineDelayedExport('elasticDump', () => new ElasticDump());
|
||||
|
||||
// creates an export for values that aren't actually avaialable until
|
||||
// until tests start to run. These getters will throw errors if the export
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { common, config, defaultTryTimeout, defaultFindTimeout, remote, shieldPage } from '../';
|
||||
import { config, defaultTryTimeout, defaultFindTimeout, remote, shieldPage } from '../';
|
||||
|
||||
export default (function () {
|
||||
var Promise = require('bluebird');
|
||||
|
@ -280,6 +280,7 @@ export default (function () {
|
|||
.setFindTimeout(defaultFindTimeout)
|
||||
.findDisplayedByCssSelector(testSubjSelector(selector));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return Common;
|
||||
|
|
200
test/support/pages/dashboard_page.js
Normal file
200
test/support/pages/dashboard_page.js
Normal file
|
@ -0,0 +1,200 @@
|
|||
import { remote, common, defaultFindTimeout } from '../';
|
||||
|
||||
export default (function () {
|
||||
var thisTime;
|
||||
|
||||
function DashboardPage() {
|
||||
this.remote = remote;
|
||||
thisTime = this.remote.setFindTimeout(defaultFindTimeout);
|
||||
}
|
||||
|
||||
DashboardPage.prototype = {
|
||||
constructor: DashboardPage,
|
||||
|
||||
clickNewDashboard: function clickNewDashboard() {
|
||||
return thisTime
|
||||
.findByCssSelector('button.ng-scope[aria-label="New Dashboard"]')
|
||||
.click();
|
||||
},
|
||||
|
||||
clickAddVisualization: function clickAddVisualization() {
|
||||
return thisTime
|
||||
.findByCssSelector('button.ng-scope[aria-label="Add a panel to the dashboard"]')
|
||||
.click();
|
||||
},
|
||||
|
||||
filterVizNames: function filterVizNames(vizName) {
|
||||
return thisTime
|
||||
.findByCssSelector('input[placeholder="Visualizations Filter..."]')
|
||||
.click()
|
||||
.pressKeys(vizName);
|
||||
},
|
||||
|
||||
clickVizNameLink: function clickVizNameLink(vizName) {
|
||||
return thisTime
|
||||
.findByLinkText(vizName)
|
||||
.click();
|
||||
},
|
||||
|
||||
closeAddVizualizationPanel: function closeAddVizualizationPanel() {
|
||||
common.debug('-------------close panel');
|
||||
return thisTime
|
||||
.findByCssSelector('i.fa fa-chevron-up')
|
||||
.click();
|
||||
},
|
||||
|
||||
addVisualization: function addVisualization(vizName) {
|
||||
var self = this;
|
||||
return this.clickAddVisualization()
|
||||
.then(function () {
|
||||
common.debug('filter visualization (' + vizName + ')');
|
||||
return self.filterVizNames(vizName);
|
||||
})
|
||||
// this second wait is usually enough to avoid the
|
||||
// 'stale element reference: element is not attached to the page document'
|
||||
// on the next step
|
||||
.then(function () {
|
||||
return common.sleep(1000);
|
||||
})
|
||||
.then(function () {
|
||||
// but wrap in a try loop since it can still happen
|
||||
return common.try(function () {
|
||||
common.debug('click visualization (' + vizName + ')');
|
||||
return self.clickVizNameLink(vizName);
|
||||
});
|
||||
})
|
||||
// this second click of 'Add' collapses the Add Visualization pane
|
||||
.then(function () {
|
||||
return self.clickAddVisualization();
|
||||
});
|
||||
},
|
||||
|
||||
saveDashboard: function saveDashboard(dashName) {
|
||||
var self = this;
|
||||
return thisTime
|
||||
.findByCssSelector('button.ng-scope[aria-label="Save Dashboard"]')
|
||||
.click()
|
||||
.then(function () {
|
||||
return common.sleep(1000);
|
||||
})
|
||||
.then(function () {
|
||||
common.debug('saveButton button clicked');
|
||||
return thisTime
|
||||
.findById('dashboardTitle')
|
||||
.type(dashName);
|
||||
})
|
||||
// click save button
|
||||
.then(function () {
|
||||
return thisTime
|
||||
.findByCssSelector('.btn-primary')
|
||||
.click();
|
||||
})
|
||||
// verify that green message at the top of the page.
|
||||
// it's only there for about 5 seconds
|
||||
.then(function () {
|
||||
return thisTime
|
||||
.findByCssSelector('kbn-truncated.toast-message.ng-isolate-scope')
|
||||
.getVisibleText();
|
||||
});
|
||||
},
|
||||
|
||||
clickDashboardByLinkText: function clickDashboardByLinkText(dashName) {
|
||||
return thisTime
|
||||
.findByLinkText(dashName)
|
||||
.click();
|
||||
},
|
||||
|
||||
// use the search filter box to narrow the results down to a single
|
||||
// entry, or at least to a single page of results
|
||||
loadSavedDashboard: function loadSavedDashboard(dashName) {
|
||||
var self = this;
|
||||
return thisTime
|
||||
.findByCssSelector('button.ng-scope[aria-label="Load Saved Dashboard"]')
|
||||
.click()
|
||||
.then(function filterDashboard() {
|
||||
common.debug('Load Saved Dashboard button clicked');
|
||||
return self.remote
|
||||
.findByCssSelector('input[name="filter"]')
|
||||
.click()
|
||||
.type(dashName.replace('-',' '));
|
||||
})
|
||||
.then(function () {
|
||||
return common.sleep(1000);
|
||||
})
|
||||
.then(function clickDashboardByLinkedText() {
|
||||
return self
|
||||
.clickDashboardByLinkText(dashName);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
getPanelTitles: function getPanelTitles() {
|
||||
common.debug('in getPanelTitles');
|
||||
return thisTime
|
||||
.findAllByCssSelector('span.panel-title')
|
||||
.then(function (titleObjects) {
|
||||
|
||||
function getTitles(chart) {
|
||||
return chart.getAttribute('title');
|
||||
}
|
||||
|
||||
var getTitlePromises = titleObjects.map(getTitles);
|
||||
return Promise.all(getTitlePromises);
|
||||
});
|
||||
},
|
||||
|
||||
getPanelData: function getPanelData() {
|
||||
common.debug('in getPanelData');
|
||||
return thisTime
|
||||
.findAllByCssSelector('li.gs-w')
|
||||
.then(function (titleObjects) {
|
||||
|
||||
function getTitles(chart) {
|
||||
var obj = {};
|
||||
return chart.getAttribute('data-col')
|
||||
.then(function (theData) {
|
||||
obj = {dataCol:theData};
|
||||
return chart;
|
||||
})
|
||||
.then(function (chart) {
|
||||
return chart.getAttribute('data-row')
|
||||
.then(function (theData) {
|
||||
obj.dataRow = theData;
|
||||
return chart;
|
||||
});
|
||||
})
|
||||
.then(function (chart) {
|
||||
return chart.getAttribute('data-sizex')
|
||||
.then(function (theData) {
|
||||
obj.dataSizeX = theData;
|
||||
return chart;
|
||||
});
|
||||
})
|
||||
.then(function (chart) {
|
||||
return chart.getAttribute('data-sizey')
|
||||
.then(function (theData) {
|
||||
obj.dataSizeY = theData;
|
||||
return chart;
|
||||
});
|
||||
})
|
||||
.then(function (chart) {
|
||||
return chart.findByCssSelector('span.panel-title')
|
||||
.then(function (titleElement) {
|
||||
return titleElement.getAttribute('title');
|
||||
})
|
||||
.then(function (theData) {
|
||||
obj.title = theData;
|
||||
return obj;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var getTitlePromises = titleObjects.map(getTitles);
|
||||
return Promise.all(getTitlePromises);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return DashboardPage;
|
||||
}());
|
|
@ -100,7 +100,21 @@ export default (function () {
|
|||
getChartInterval: function getChartInterval() {
|
||||
return thisTime
|
||||
.findByCssSelector('a[ng-click="toggleInterval()"]')
|
||||
.getVisibleText();
|
||||
.getVisibleText()
|
||||
.then(function (intervalText) {
|
||||
if (intervalText.length > 0) {
|
||||
return intervalText;
|
||||
} else {
|
||||
return thisTime
|
||||
.findByCssSelector('select[ng-model="state.interval"]')
|
||||
.getProperty('value') // this gets 'string:d' for Daily
|
||||
.then(function (selectedValue) {
|
||||
return thisTime
|
||||
.findByCssSelector('option[value="' + selectedValue + '"]')
|
||||
.getVisibleText();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setChartInterval: function setChartInterval(interval) {
|
||||
|
@ -211,6 +225,24 @@ export default (function () {
|
|||
return thisTime
|
||||
.findByClassName('sidebar-list')
|
||||
.getProperty('clientWidth');
|
||||
},
|
||||
|
||||
hasNoResults: function hasNoResults() {
|
||||
return common
|
||||
.findTestSubject('discoverNoResults')
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
},
|
||||
|
||||
getNoResultsTimepicker: function getNoResultsTimepicker() {
|
||||
return common.findTestSubject('discoverNoResultsTimefilter');
|
||||
},
|
||||
|
||||
hasNoResultsTimepicker: function hasNoResultsTimepicker() {
|
||||
return this
|
||||
.getNoResultsTimepicker()
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -48,6 +48,13 @@ export default (function () {
|
|||
.findDisplayedByClassName('navbar-timepicker-time-desc').click();
|
||||
},
|
||||
|
||||
isTimepickerOpen: function isTimepickerOpen() {
|
||||
return this.remote.setFindTimeout(defaultFindTimeout)
|
||||
.findDisplayedByCssSelector('.kbn-timepicker')
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
},
|
||||
|
||||
clickAbsoluteButton: function clickAbsoluteButton() {
|
||||
return this.remote.setFindTimeout(defaultFindTimeout)
|
||||
.findByLinkText('Absolute').click();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue