mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Merge branch '4.2' of github.com:elastic/kibana into 4.2
This commit is contained in:
commit
60cc2393fb
52 changed files with 1883 additions and 154 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -10,6 +10,7 @@ target
|
|||
.idea
|
||||
*.iml
|
||||
*.log
|
||||
/test/output
|
||||
/esvm
|
||||
.htpasswd
|
||||
installedPlugins
|
||||
|
@ -18,3 +19,4 @@ webpackstats.json
|
|||
config/kibana.dev.yml
|
||||
coverage
|
||||
selenium
|
||||
.babelcache.json
|
||||
|
|
|
@ -102,6 +102,41 @@ The standard `npm run test` task runs several sub tasks and can take several min
|
|||
</dd>
|
||||
</dl>
|
||||
|
||||
### Functional UI Testing
|
||||
|
||||
#### Handy references
|
||||
|
||||
- https://theintern.github.io/
|
||||
- https://theintern.github.io/leadfoot/Element.html
|
||||
|
||||
#### Running tests using npm task:
|
||||
|
||||
*The Selenium server that is started currently only runs the tests in Firefox*
|
||||
|
||||
To runt the functional UI tests, execute the following command:
|
||||
|
||||
`npm run test:ui`
|
||||
|
||||
The task above takes a little time to start the servers. You can also start the servers and leave them running, and then run the tests separately:
|
||||
|
||||
`npm run test:ui:server` will start the server required to run the selenium tests, leave this open
|
||||
|
||||
`npm run test:ui:runner` will run the frontend tests and close when complete
|
||||
|
||||
#### Running tests locally with your existing (and already running) ElasticSearch, Kibana, and Selenium Server:
|
||||
|
||||
Set your es and kibana ports in `test/intern.js` to 9220 and 5620, respecitively. You can configure your Selenium server to run the tests on Chrome,IE, or other browsers here.
|
||||
|
||||
Once you've got the services running, execute the following:
|
||||
|
||||
`npm run test:ui:runner`
|
||||
|
||||
#### General notes:
|
||||
|
||||
- Using Page Objects pattern (https://theintern.github.io/intern/#writing-functional-test)
|
||||
- At least the initial tests for the Settings, Discover, and Visualize tabs all depend on a very specific set of logstash-type data (generated with makelogs). Since that is a static set of data, all the Discover and Visualize tests use a specific Absolute time range. This gaurantees the same results each run.
|
||||
- These tests have been developed and tested with Chrome and Firefox browser. In theory, they should work on all browsers (that's the benefit of Intern using Leadfoot).
|
||||
- These tests should also work with an external testing service like https://saucelabs.com/ or https://www.browserstack.com/ but that has not been tested.
|
||||
|
||||
### Submit a pull request
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ module.exports = function (grunt) {
|
|||
lintThese: [
|
||||
'Gruntfile.js',
|
||||
'<%= root %>/tasks/**/*.js',
|
||||
'<%= root %>/test/**/*.js',
|
||||
'<%= src %>/**/*.js',
|
||||
'!<%= src %>/fixtures/**/*.js'
|
||||
],
|
||||
|
|
10
README.md
10
README.md
|
@ -1,4 +1,4 @@
|
|||
# Kibana 4.2.1-snapshot
|
||||
# Kibana 4.2.1
|
||||
|
||||
[](https://travis-ci.org/elastic/kibana?branch=master)
|
||||
|
||||
|
@ -39,7 +39,7 @@ For the daring, snapshot builds are available. These builds are created after ea
|
|||
|
||||
| platform | | |
|
||||
| --- | --- | --- |
|
||||
| OSX | [tar](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-snapshot-darwin-x64.tar.gz) | [zip](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-snapshot-darwin-x64.zip) |
|
||||
| Linux x64 | [tar](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-snapshot-linux-x64.tar.gz) | [zip](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-snapshot-linux-x64.zip) |
|
||||
| Linux x86 | [tar](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-snapshot-linux-x86.tar.gz) | [zip](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-snapshot-linux-x86.zip) |
|
||||
| Windows | [tar](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-snapshot-windows.tar.gz) | [zip](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-snapshot-windows.zip) |
|
||||
| OSX | [tar](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-darwin-x64.tar.gz) | [zip](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-darwin-x64.zip) |
|
||||
| Linux x64 | [tar](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-linux-x64.tar.gz) | [zip](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-linux-x64.zip) |
|
||||
| Linux x86 | [tar](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-linux-x86.tar.gz) | [zip](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-linux-x86.zip) |
|
||||
| Windows | [tar](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-windows.tar.gz) | [zip](http://download.elastic.co/kibana/kibana-snapshot/kibana-4.2.1-windows.zip) |
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
# The host to bind the server to.
|
||||
# server.host: "0.0.0.0"
|
||||
|
||||
# A value to use as a XSRF token. This token is sent back to the server on each request
|
||||
# and required if you want to execute requests from other clients (like curl).
|
||||
# server.xsrf.token: ""
|
||||
|
||||
# The Elasticsearch instance to use for all your queries.
|
||||
# elasticsearch.url: "http://localhost:9200"
|
||||
|
||||
|
|
|
@ -102,8 +102,6 @@ Move the cursor to the bottom right corner of the container until the cursor cha
|
|||
cursor changes, click and drag the corner of the container to change the container's size. Release the mouse button to
|
||||
confirm the new container size.
|
||||
|
||||
// enhancement request: a way to specify specific dimensions for a container in pixels, or at least display that info?
|
||||
|
||||
[float]
|
||||
[[removing-containers]]
|
||||
==== Removing Containers
|
||||
|
|
|
@ -30,7 +30,7 @@ The tutorials in this section rely on the following data sets:
|
|||
* A set of fictitious accounts with randomly generated data. Download this data set by clicking here:
|
||||
https://github.com/bly2k/files/blob/master/accounts.zip?raw=true[accounts.zip]
|
||||
* A set of randomly generated log files. Download this data set by clicking here:
|
||||
https://download.elastic.co/demos/kibana/gettingstarted/logs.jsonl.gz[logstash.jsonl.gz]
|
||||
https://download.elastic.co/demos/kibana/gettingstarted/logs.jsonl.gz[logs.jsonl.gz]
|
||||
|
||||
Two of the data sets are compressed. Use the following commands to extract the files:
|
||||
|
||||
|
@ -103,15 +103,77 @@ This mapping specifies the following qualities for the data set:
|
|||
* The _speaker_ field is a string that isn't analyzed. The string in this field is treated as a single unit, even if
|
||||
there are multiple words in the field.
|
||||
* The same applies to the _play_name_ field.
|
||||
* The line_id and speech_number fields are integers.
|
||||
* The _line_id_ and _speech_number_ fields are integers.
|
||||
|
||||
The accounts and logstash data sets don't require any mappings, so at this point we're ready to load the data sets into
|
||||
Elasticsearch with the following commands:
|
||||
The logs data set requires a mapping to label the latitude/longitude pairs in the logs as geographic locations by
|
||||
applying the `geo_point` type to those fields.
|
||||
|
||||
Use the following commands to establish `geo_point` mapping for the logs:
|
||||
|
||||
[source,shell]
|
||||
curl -XPOST 'localhost:9200/bank/_bulk?pretty' --data-binary @accounts.json
|
||||
curl -XPUT http://localhost:9200/logstash-2015.05.18 -d '
|
||||
{
|
||||
"mappings": {
|
||||
"log": {
|
||||
"properties": {
|
||||
"geo": {
|
||||
"properties": {
|
||||
"coordinates": {
|
||||
"type": "geo_point"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
[source,shell]
|
||||
curl -XPUT http://localhost:9200/logstash-2015.05.19 -d '
|
||||
{
|
||||
"mappings": {
|
||||
"log": {
|
||||
"properties": {
|
||||
"geo": {
|
||||
"properties": {
|
||||
"coordinates": {
|
||||
"type": "geo_point"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
[source,shell]
|
||||
curl -XPUT http://localhost:9200/logstash-2015.05.20 -d '
|
||||
{
|
||||
"mappings": {
|
||||
"log": {
|
||||
"properties": {
|
||||
"geo": {
|
||||
"properties": {
|
||||
"coordinates": {
|
||||
"type": "geo_point"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
The accounts data set doesn't require any mappings, so at this point we're ready to use the Elasticsearch
|
||||
{ref}/docs-bulk.html[`bulk`] API to load the data sets with the following commands:
|
||||
|
||||
[source,shell]
|
||||
curl -XPOST 'localhost:9200/bank/account/_bulk?pretty' --data-binary @accounts.json
|
||||
curl -XPOST 'localhost:9200/shakespeare/_bulk?pretty' --data-binary @shakespeare.json
|
||||
curl -XPOST 'localhost:9200/_bulk?pretty' --data-binary @logstash.json
|
||||
curl -XPOST 'localhost:9200/_bulk?pretty' --data-binary @logs.jsonl
|
||||
|
||||
These commands may take some time to execute, depending on the computing resources available.
|
||||
|
||||
|
@ -133,16 +195,23 @@ yellow open logstash-2015.05.20 5 1 4750 0 16.4mb
|
|||
[[tutorial-define-index]]
|
||||
=== Defining Your Index Patterns
|
||||
|
||||
Each set of data loaded to Elasticsearch has an https://www.elastic.co/guide/en/kibana/current/settings.html#settings-create-pattern[index pattern]. In the previous section, the Shakespeare data set has an index named `shakespeare`, and the accounts
|
||||
Each set of data loaded to Elasticsearch has an
|
||||
https://www.elastic.co/guide/en/kibana/current/settings.html#settings-create-pattern[index pattern]. In the previous
|
||||
section, the Shakespeare data set has an index named `shakespeare`, and the accounts
|
||||
data set has an index named `bank`. An _index pattern_ is a string with optional wildcards that can match multiple
|
||||
indices. For example, in the common logging use case, a typical index name contains the date in MM-DD-YYYY
|
||||
format, and an index pattern for May would look something like `logstash-2015.05*`.
|
||||
|
||||
For this tutorial, any pattern that matches either of the two indices we've loaded will work. Open a browser and
|
||||
For this tutorial, any pattern that matches the name of an index we've loaded will work. Open a browser and
|
||||
navigate to `localhost:5601`. Click the *Settings* tab, then the *Indices* tab. Click *Add New* to define a new index
|
||||
pattern. Since these data sets don't contain time-series data, make sure the *Index contains time-based events* box is
|
||||
unchecked. Specify `shakes*` as the index pattern for the Shakespeare data set and click *Create* to define the index
|
||||
pattern, then define a second index pattern named `ba*`.
|
||||
pattern. Two of the sample data sets, the Shakespeare plays and the financial accounts, don't contain time-series data.
|
||||
Make sure the *Index contains time-based events* box is unchecked when you create index patterns for these data sets.
|
||||
Specify `shakes*` as the index pattern for the Shakespeare data set and click *Create* to define the index pattern, then
|
||||
define a second index pattern named `ba*`.
|
||||
|
||||
The Logstash data set does contain time-series data, so after clicking *Add New* to define the index for this data
|
||||
set, make sure the *Index contains time-based events* box is checked and select the `@timestamp` field from the
|
||||
*Time-field name* drop-down.
|
||||
|
||||
[float]
|
||||
[[tutorial-discovering]]
|
||||
|
@ -213,7 +282,7 @@ total number of ranges to six. Enter the following ranges:
|
|||
15000 30999
|
||||
31000 50000
|
||||
|
||||
Click the green *Apply changes* to display the chart:
|
||||
Click the green *Apply changes* button image:images/apply-changes-button.png[] to display the chart:
|
||||
|
||||
image::images/tutorial-visualize-pie-2.png[]
|
||||
|
||||
|
@ -221,8 +290,8 @@ This shows you what proportion of the 1000 accounts fall in these balance ranges
|
|||
we're going to add another bucket aggregation. We can break down each of the balance ranges further by the account
|
||||
holder's age.
|
||||
|
||||
Click *Add sub-buckets* at the bottom, then select the *Terms* aggregation and the *age* field from the drop-downs.
|
||||
Click the green *Apply changes* button to add an external ring with the new results.
|
||||
Click *Add sub-buckets* at the bottom, then select *Split Slices*. Choose the *Terms* aggregation and the *age* field from the drop-downs.
|
||||
Click the green *Apply changes* button image:images/apply-changes-button.png[] to add an external ring with the new results.
|
||||
|
||||
image::images/tutorial-visualize-pie-3.png[]
|
||||
|
||||
|
@ -237,9 +306,9 @@ image::images/tutorial-visualize-bar-1.png[]
|
|||
For the Y-axis metrics aggregation, select *Unique Count*, with *speaker* as the field. For Shakespeare plays, it might
|
||||
be useful to know which plays have the lowest number of distinct speaking parts, if your theater company is short on
|
||||
actors. For the X-Axis buckets, select the *Terms* aggregation with the *play_name* field. For the *Order*, select
|
||||
*Bottom*, leaving the *Size* at 5.
|
||||
*Ascending*, leaving the *Size* at 5.
|
||||
|
||||
Leave the other elements at their default values and click the green *Apply changes* button. Your chart should now look
|
||||
Leave the other elements at their default values and click the green *Apply changes* button image:images/apply-changes-button.png[]. Your chart should now look
|
||||
like this:
|
||||
|
||||
image::images/tutorial-visualize-bar-2.png[]
|
||||
|
@ -254,7 +323,7 @@ as well as change many other options for your visualizations, by clicking the *O
|
|||
Now that you have a list of the smallest casts for Shakespeare plays, you might also be curious to see which of these
|
||||
plays makes the greatest demands on an individual actor by showing the maximum number of speeches for a given part. Add
|
||||
a Y-axis aggregation with the *Add metrics* button, then choose the *Max* aggregation for the *speech_number* field. In
|
||||
the *Options* tab, change the *Bar Mode* drop-down to *grouped*, then click the green *Apply changes* button. Your
|
||||
the *Options* tab, change the *Bar Mode* drop-down to *grouped*, then click the green *Apply changes* button image:images/apply-changes-button.png[]. Your
|
||||
chart should now look like this:
|
||||
|
||||
image::images/tutorial-visualize-bar-3.png[]
|
||||
|
@ -265,9 +334,9 @@ might therefore make more demands on an actor's memory.
|
|||
Save this chart with the name _Bar Example_.
|
||||
|
||||
Next, we're going to make a tile map chart to visualize some geographic data. Click on *New Visualization*, then
|
||||
*Tile map*. Select *From a new search* and the `logstash-*` index pattern. Define the time window for the events we're
|
||||
exploring by clicking the time selector at the top right of the Kibana interface. Click on *Absolute*, then set the
|
||||
end time for the range to May 20, 2015 and the start time to May 18, 2015:
|
||||
*Tile map*. Select *From a new search* and the `logstash-*` index pattern. Define the time window for the events
|
||||
we're exploring by clicking the time selector at the top right of the Kibana interface. Click on *Absolute*, then set
|
||||
the start time to May 18, 2015 and the end time for the range to May 20, 2015:
|
||||
|
||||
image::images/tutorial-timepicker.png[]
|
||||
|
||||
|
@ -276,7 +345,7 @@ at the bottom. You'll see a map of the world, since we haven't defined any bucke
|
|||
|
||||
image::images/tutorial-visualize-map-1.png[]
|
||||
|
||||
Select *Geo Coordinates* as the bucket, then click the green *Apply changes* button. Your chart should now look like
|
||||
Select *Geo Coordinates* as the bucket, then click the green *Apply changes* button image:images/apply-changes-button.png[]. Your chart should now look like
|
||||
this:
|
||||
|
||||
image::images/tutorial-visualize-map-2.png[]
|
||||
|
@ -304,7 +373,7 @@ Write the following text in the field:
|
|||
The Markdown widget uses **markdown** syntax.
|
||||
> Blockquotes in Markdown use the > character.
|
||||
|
||||
Click the green *Apply changes* button to display the rendered Markdown in the preview pane:
|
||||
Click the green *Apply changes* button image:images/apply-changes-button.png[] to display the rendered Markdown in the preview pane:
|
||||
|
||||
image::images/tutorial-visualize-md-2.png[]
|
||||
|
||||
|
|
BIN
docs/images/apply-changes-button.png
Normal file
BIN
docs/images/apply-changes-button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 238 KiB |
|
@ -60,8 +60,8 @@ To encrypt communications between the browser and the Kibana server, you configu
|
|||
[source,text]
|
||||
----
|
||||
# SSL for outgoing requests from the Kibana Server (PEM formatted)
|
||||
ssl_key_file: /path/to/your/server.key
|
||||
ssl_cert_file: /path/to/your/server.crt
|
||||
server.ssl.key: /path/to/your/server.key
|
||||
server.ssl.cert: /path/to/your/server.crt
|
||||
----
|
||||
|
||||
If you are using Shield or a proxy that provides an HTTPS endpoint for Elasticsearch,
|
||||
|
|
0
optimize/.empty
Normal file
0
optimize/.empty
Normal file
|
@ -11,7 +11,7 @@
|
|||
"dashboarding"
|
||||
],
|
||||
"private": false,
|
||||
"version": "4.2.1-snapshot",
|
||||
"version": "4.2.1",
|
||||
"build": {
|
||||
"number": 8467,
|
||||
"sha": "6cb7fec4e154faa0a4a3fee4b33dfef91b9870d9"
|
||||
|
@ -167,7 +167,7 @@
|
|||
"npm": "2.11.0",
|
||||
"portscanner": "1.0.0",
|
||||
"simple-git": "1.8.0",
|
||||
"sinon": "1.16.1",
|
||||
"sinon": "1.17.2",
|
||||
"source-map": "0.4.4",
|
||||
"wreck": "6.2.0"
|
||||
},
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
require('babel/register')(require('../optimize/babelOptions').node);
|
||||
// load the babel options seperately so that they can modify the process.env
|
||||
// before calling babel/register
|
||||
const babelOptions = require('../optimize/babelOptions').node;
|
||||
require('babel/register')(babelOptions);
|
||||
require('./cli');
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
var fromRoot = require('requirefrom')('src/utils')('fromRoot');
|
||||
var cloneDeep = require('lodash').cloneDeep;
|
||||
var fromRoot = require('path').resolve.bind(null, __dirname, '../../');
|
||||
|
||||
if (!process.env.BABEL_CACHE_PATH) {
|
||||
process.env.BABEL_CACHE_PATH = fromRoot('optimize/.babelcache.json');
|
||||
}
|
||||
|
||||
exports.webpack = {
|
||||
stage: 1,
|
||||
|
@ -6,7 +11,7 @@ exports.webpack = {
|
|||
optional: ['runtime']
|
||||
};
|
||||
|
||||
exports.node = Object.assign({
|
||||
exports.node = cloneDeep({
|
||||
ignore: [
|
||||
fromRoot('src'),
|
||||
/[\\\/](node_modules|bower_components)[\\\/]/
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
var cloneDeep = require('lodash').cloneDeep;
|
||||
var fromRoot = require('path').resolve.bind(null, __dirname, '../../');
|
||||
|
||||
if (!process.env.BABEL_CACHE_PATH) {
|
||||
process.env.BABEL_CACHE_PATH = fromRoot('optimize/.babelcache.json');
|
||||
}
|
||||
|
||||
exports.webpack = {
|
||||
stage: 1,
|
||||
nonStandard: false,
|
||||
optional: ['runtime']
|
||||
};
|
||||
|
||||
exports.node = Object.assign({}, exports.webpack);
|
||||
exports.node = cloneDeep(exports.webpack);
|
||||
|
|
|
@ -13,7 +13,12 @@ describe('plugins/elasticsearch', function () {
|
|||
|
||||
before(function () {
|
||||
kbnServer = new KbnServer({
|
||||
server: { autoListen: false },
|
||||
server: {
|
||||
autoListen: false,
|
||||
xsrf: {
|
||||
disableProtection: true
|
||||
}
|
||||
},
|
||||
logging: { quiet: true },
|
||||
plugins: {
|
||||
scanDirs: [
|
||||
|
@ -104,5 +109,3 @@ describe('plugins/elasticsearch', function () {
|
|||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@ let path = require('path');
|
|||
|
||||
let utils = require('requirefrom')('src/utils');
|
||||
let fromRoot = utils('fromRoot');
|
||||
const randomBytes = require('crypto').randomBytes;
|
||||
|
||||
module.exports = Joi.object({
|
||||
module.exports = () => Joi.object({
|
||||
pkg: Joi.object({
|
||||
version: Joi.string().default(Joi.ref('$version')),
|
||||
buildNum: Joi.number().default(Joi.ref('$buildNum')),
|
||||
|
@ -39,7 +40,11 @@ module.exports = Joi.object({
|
|||
origin: ['*://localhost:9876'] // karma test server
|
||||
}),
|
||||
otherwise: Joi.boolean().default(false)
|
||||
})
|
||||
}),
|
||||
xsrf: Joi.object({
|
||||
token: Joi.string().default(randomBytes(32).toString('hex')),
|
||||
disableProtection: Joi.boolean().default(false),
|
||||
}).default(),
|
||||
}).default(),
|
||||
|
||||
logging: Joi.object().keys({
|
||||
|
@ -106,4 +111,3 @@ module.exports = Joi.object({
|
|||
}).default()
|
||||
|
||||
}).default();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = function (kbnServer) {
|
||||
let Config = require('./Config');
|
||||
let schema = require('./schema');
|
||||
let schema = require('./schema')();
|
||||
|
||||
kbnServer.config = new Config(schema, kbnServer.settings || {});
|
||||
};
|
||||
|
|
145
src/server/http/__tests__/xsrf.js
Normal file
145
src/server/http/__tests__/xsrf.js
Normal file
|
@ -0,0 +1,145 @@
|
|||
import expect from 'expect.js';
|
||||
import { fromNode as fn } from 'bluebird';
|
||||
import { resolve } from 'path';
|
||||
|
||||
import KbnServer from '../../KbnServer';
|
||||
|
||||
const nonDestructiveMethods = ['GET'];
|
||||
const destructiveMethods = ['POST', 'PUT', 'DELETE'];
|
||||
const src = resolve.bind(null, __dirname, '../../../../src');
|
||||
|
||||
describe('xsrf request filter', function () {
|
||||
function inject(kbnServer, opts) {
|
||||
return fn(cb => {
|
||||
kbnServer.server.inject(opts, (resp) => {
|
||||
cb(null, resp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const makeServer = async function (token) {
|
||||
const kbnServer = new KbnServer({
|
||||
server: { autoListen: false, xsrf: { token } },
|
||||
plugins: { scanDirs: [src('plugins')] },
|
||||
logging: { quiet: true },
|
||||
optimize: { enabled: false },
|
||||
});
|
||||
|
||||
await kbnServer.ready();
|
||||
|
||||
kbnServer.server.route({
|
||||
path: '/xsrf/test/route',
|
||||
method: [...nonDestructiveMethods, ...destructiveMethods],
|
||||
handler: function (req, reply) {
|
||||
reply(null, 'ok');
|
||||
}
|
||||
});
|
||||
|
||||
return kbnServer;
|
||||
};
|
||||
|
||||
describe('issuing tokens', function () {
|
||||
const token = 'secur3';
|
||||
let kbnServer;
|
||||
beforeEach(async () => kbnServer = await makeServer(token));
|
||||
afterEach(async () => await kbnServer.close());
|
||||
|
||||
it('sends a token when rendering an app', async function () {
|
||||
var resp = await inject(kbnServer, {
|
||||
method: 'GET',
|
||||
url: '/app/kibana',
|
||||
});
|
||||
|
||||
expect(resp.payload).to.contain(`"xsrfToken":"${token}"`);
|
||||
});
|
||||
});
|
||||
|
||||
context('without configured token', function () {
|
||||
let kbnServer;
|
||||
beforeEach(async () => kbnServer = await makeServer());
|
||||
afterEach(async () => await kbnServer.close());
|
||||
|
||||
it('responds with a random token', async function () {
|
||||
var resp = await inject(kbnServer, {
|
||||
method: 'GET',
|
||||
url: '/app/kibana',
|
||||
});
|
||||
|
||||
expect(resp.payload).to.match(/"xsrfToken":".{64}"/);
|
||||
});
|
||||
});
|
||||
|
||||
context('with configured token', function () {
|
||||
const token = 'mytoken';
|
||||
let kbnServer;
|
||||
beforeEach(async () => kbnServer = await makeServer(token));
|
||||
afterEach(async () => await kbnServer.close());
|
||||
|
||||
for (const method of nonDestructiveMethods) {
|
||||
context(`nonDestructiveMethod: ${method}`, function () { // eslint-disable-line no-loop-func
|
||||
it('accepts requests without a token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.payload).to.be('ok');
|
||||
});
|
||||
|
||||
it('ignores invalid tokens', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
'kbn-xsrf-token': `invalid:${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.headers).to.not.have.property('kbn-xsrf-token');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const method of destructiveMethods) {
|
||||
context(`destructiveMethod: ${method}`, function () { // eslint-disable-line no-loop-func
|
||||
it('accepts requests with the correct token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
'kbn-xsrf-token': token,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(200);
|
||||
expect(resp.payload).to.be('ok');
|
||||
});
|
||||
|
||||
it('rejects requests without a token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(403);
|
||||
expect(resp.payload).to.match(/"Missing XSRF token"/);
|
||||
});
|
||||
|
||||
it('rejects requests with an invalid token', async function () {
|
||||
const resp = await inject(kbnServer, {
|
||||
url: '/xsrf/test/route',
|
||||
method: method,
|
||||
headers: {
|
||||
'kbn-xsrf-token': `invalid:${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resp.statusCode).to.be(403);
|
||||
expect(resp.payload).to.match(/"Invalid XSRF token"/);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -119,4 +119,6 @@ module.exports = function (kbnServer, server, config) {
|
|||
.permanent(true);
|
||||
}
|
||||
});
|
||||
|
||||
return kbnServer.mixin(require('./xsrf'));
|
||||
};
|
||||
|
|
20
src/server/http/xsrf.js
Normal file
20
src/server/http/xsrf.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { forbidden } from 'boom';
|
||||
|
||||
export default function (kbnServer, server, config) {
|
||||
const token = config.get('server.xsrf.token');
|
||||
const disabled = config.get('server.xsrf.disableProtection');
|
||||
|
||||
server.decorate('reply', 'issueXsrfToken', function () {
|
||||
return token;
|
||||
});
|
||||
|
||||
server.ext('onPostAuth', function (req, reply) {
|
||||
if (disabled || req.method === 'get') return reply.continue();
|
||||
|
||||
const attempt = req.headers['kbn-xsrf-token'];
|
||||
if (!attempt) return reply(forbidden('Missing XSRF token'));
|
||||
if (attempt !== token) return reply(forbidden('Invalid XSRF token'));
|
||||
|
||||
return reply.continue();
|
||||
});
|
||||
}
|
|
@ -21,12 +21,13 @@ module.exports = class KbnLogger {
|
|||
}
|
||||
|
||||
init(readstream, emitter, callback) {
|
||||
readstream
|
||||
.pipe(this.squeeze)
|
||||
.pipe(this.format)
|
||||
.pipe(this.dest);
|
||||
|
||||
emitter.on('stop', _.noop);
|
||||
this.output = readstream.pipe(this.squeeze).pipe(this.format);
|
||||
this.output.pipe(this.dest);
|
||||
|
||||
emitter.on('stop', () => {
|
||||
this.output.unpipe(this.dest);
|
||||
});
|
||||
|
||||
callback();
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ module.exports = async function (kbnServer, server, config) {
|
|||
|
||||
|
||||
let path = [];
|
||||
async function initialize(id) {
|
||||
const initialize = async function (id) {
|
||||
let plugin = plugins.byId[id];
|
||||
|
||||
if (includes(path, id)) {
|
||||
|
|
|
@ -64,13 +64,14 @@ module.exports = async (kbnServer, server, config) => {
|
|||
}
|
||||
|
||||
server.decorate('reply', 'renderApp', function (app) {
|
||||
let payload = {
|
||||
const payload = {
|
||||
app: app,
|
||||
nav: uiExports.apps,
|
||||
version: kbnServer.version,
|
||||
buildNum: config.get('pkg.buildNum'),
|
||||
buildSha: config.get('pkg.buildSha'),
|
||||
vars: defaults(app.getInjectedVars(), defaultInjectedVars),
|
||||
xsrfToken: this.issueXsrfToken(),
|
||||
};
|
||||
|
||||
return this.view(app.templateName, {
|
||||
|
|
132
src/ui/public/chrome/api/__tests__/xsrf.js
Normal file
132
src/ui/public/chrome/api/__tests__/xsrf.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
import $ from 'jquery';
|
||||
import expect from 'expect.js';
|
||||
import { stub } from 'auto-release-sinon';
|
||||
import ngMock from 'ngMock';
|
||||
|
||||
import xsrfChromeApi from '../xsrf';
|
||||
|
||||
const xsrfHeader = 'kbn-xsrf-token';
|
||||
const xsrfToken = 'xsrfToken';
|
||||
|
||||
describe('chrome xsrf apis', function () {
|
||||
describe('#getXsrfToken()', function () {
|
||||
it('exposes the token', function () {
|
||||
const chrome = {};
|
||||
xsrfChromeApi(chrome, { xsrfToken });
|
||||
expect(chrome.getXsrfToken()).to.be(xsrfToken);
|
||||
});
|
||||
});
|
||||
|
||||
context('jQuery support', function () {
|
||||
it('adds a global jQuery prefilter', function () {
|
||||
stub($, 'ajaxPrefilter');
|
||||
xsrfChromeApi({}, {});
|
||||
expect($.ajaxPrefilter.callCount).to.be(1);
|
||||
});
|
||||
|
||||
context('jQuery prefilter', function () {
|
||||
let prefilter;
|
||||
const xsrfToken = 'xsrfToken';
|
||||
|
||||
beforeEach(function () {
|
||||
stub($, 'ajaxPrefilter');
|
||||
xsrfChromeApi({}, { xsrfToken });
|
||||
prefilter = $.ajaxPrefilter.args[0][0];
|
||||
});
|
||||
|
||||
it('sets the kbn-xsrf-token header', function () {
|
||||
const setHeader = stub();
|
||||
prefilter({}, {}, { setRequestHeader: setHeader });
|
||||
|
||||
expect(setHeader.callCount).to.be(1);
|
||||
expect(setHeader.args[0]).to.eql([
|
||||
xsrfHeader,
|
||||
xsrfToken
|
||||
]);
|
||||
});
|
||||
|
||||
it('can be canceled by setting the kbnXsrfToken option', function () {
|
||||
const setHeader = stub();
|
||||
prefilter({ kbnXsrfToken: false }, {}, { setRequestHeader: setHeader });
|
||||
expect(setHeader.callCount).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
context('Angular support', function () {
|
||||
|
||||
let $http;
|
||||
let $httpBackend;
|
||||
|
||||
beforeEach(function () {
|
||||
stub($, 'ajaxPrefilter');
|
||||
const chrome = {};
|
||||
xsrfChromeApi(chrome, { xsrfToken });
|
||||
ngMock.module(chrome.$setupXsrfRequestInterceptor);
|
||||
});
|
||||
|
||||
beforeEach(ngMock.inject(function ($injector) {
|
||||
$http = $injector.get('$http');
|
||||
$httpBackend = $injector.get('$httpBackend');
|
||||
|
||||
$httpBackend
|
||||
.when('POST', '/api/test')
|
||||
.respond('ok');
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
$httpBackend.verifyNoOutstandingExpectation();
|
||||
$httpBackend.verifyNoOutstandingRequest();
|
||||
});
|
||||
|
||||
it('injects a kbn-xsrf-token header on every request', function () {
|
||||
$httpBackend.expectPOST('/api/test', undefined, function (headers) {
|
||||
return headers[xsrfHeader] === xsrfToken;
|
||||
}).respond(200, '');
|
||||
|
||||
$http.post('/api/test');
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('skips requests with the kbnXsrfToken set falsey', function () {
|
||||
$httpBackend.expectPOST('/api/test', undefined, function (headers) {
|
||||
return !(xsrfHeader in headers);
|
||||
}).respond(200, '');
|
||||
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: '/api/test',
|
||||
kbnXsrfToken: 0
|
||||
});
|
||||
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: '/api/test',
|
||||
kbnXsrfToken: ''
|
||||
});
|
||||
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: '/api/test',
|
||||
kbnXsrfToken: false
|
||||
});
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
|
||||
it('accepts alternate tokens to use', function () {
|
||||
const customToken = `custom:${xsrfToken}`;
|
||||
$httpBackend.expectPOST('/api/test', undefined, function (headers) {
|
||||
return headers[xsrfHeader] === customToken;
|
||||
}).respond(200, '');
|
||||
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: '/api/test',
|
||||
kbnXsrfToken: customToken
|
||||
});
|
||||
|
||||
$httpBackend.flush();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
1
src/ui/public/chrome/api/angular.js
vendored
1
src/ui/public/chrome/api/angular.js
vendored
|
@ -24,6 +24,7 @@ module.exports = function (chrome, internals) {
|
|||
a.href = '/elasticsearch';
|
||||
return a.href;
|
||||
}()))
|
||||
.config(chrome.$setupXsrfRequestInterceptor)
|
||||
.directive('kbnChrome', function ($rootScope) {
|
||||
return {
|
||||
template: function ($el) {
|
||||
|
|
29
src/ui/public/chrome/api/xsrf.js
Normal file
29
src/ui/public/chrome/api/xsrf.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import $ from 'jquery';
|
||||
import { set } from 'lodash';
|
||||
|
||||
export default function (chrome, internals) {
|
||||
|
||||
chrome.getXsrfToken = function () {
|
||||
return internals.xsrfToken;
|
||||
};
|
||||
|
||||
$.ajaxPrefilter(function ({ kbnXsrfToken = internals.xsrfToken }, originalOptions, jqXHR) {
|
||||
if (kbnXsrfToken) {
|
||||
jqXHR.setRequestHeader('kbn-xsrf-token', kbnXsrfToken);
|
||||
}
|
||||
});
|
||||
|
||||
chrome.$setupXsrfRequestInterceptor = function ($httpProvider) {
|
||||
$httpProvider.interceptors.push(function () {
|
||||
return {
|
||||
request: function (opts) {
|
||||
const { kbnXsrfToken = internals.xsrfToken } = opts;
|
||||
if (kbnXsrfToken) {
|
||||
set(opts, ['headers', 'kbn-xsrf-token'], kbnXsrfToken);
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
|
@ -18,6 +18,7 @@ var internals = _.defaults(
|
|||
rootController: null,
|
||||
rootTemplate: null,
|
||||
showAppsLink: null,
|
||||
xsrfToken: null,
|
||||
brand: null,
|
||||
nav: [],
|
||||
applicationClasses: []
|
||||
|
@ -30,6 +31,7 @@ $('<link>').attr({
|
|||
}).appendTo('head');
|
||||
|
||||
require('./api/apps')(chrome, internals);
|
||||
require('./api/xsrf')(chrome, internals);
|
||||
require('./api/nav')(chrome, internals);
|
||||
require('./api/angular')(chrome, internals);
|
||||
require('./api/controls')(chrome, internals);
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
/* global mocha */
|
||||
|
||||
// chrome expects to be loaded first, let it get its way
|
||||
var chrome = require('ui/chrome');
|
||||
|
||||
var Nonsense = require('Nonsense');
|
||||
var sinon = require('sinon');
|
||||
var $ = require('jquery');
|
||||
|
@ -6,8 +10,6 @@ var _ = require('lodash');
|
|||
var parse = require('url').parse;
|
||||
|
||||
var StackTraceMapper = require('ui/StackTraceMapper');
|
||||
var chrome = require('ui/chrome');
|
||||
|
||||
|
||||
/*** the vislib tests have certain style requirements, so lets make sure they are met ***/
|
||||
$('body').attr('id', 'test-harness-body'); // so we can make high priority selectors
|
||||
|
|
|
@ -668,16 +668,21 @@ define(function (require) {
|
|||
* @return {undefined}
|
||||
*/
|
||||
Data.prototype._normalizeOrdered = function () {
|
||||
if (!this.data.ordered || !this.data.ordered.date) return;
|
||||
var data = this.getVisData();
|
||||
var self = this;
|
||||
|
||||
var missingMin = this.data.ordered.min == null;
|
||||
var missingMax = this.data.ordered.max == null;
|
||||
data.forEach(function (d) {
|
||||
if (!d.ordered || !d.ordered.date) return;
|
||||
|
||||
if (missingMax || missingMin) {
|
||||
var extent = d3.extent(this.xValues());
|
||||
if (missingMin) this.data.ordered.min = extent[0];
|
||||
if (missingMax) this.data.ordered.max = extent[1];
|
||||
}
|
||||
var missingMin = d.ordered.min == null;
|
||||
var missingMax = d.ordered.max == null;
|
||||
|
||||
if (missingMax || missingMin) {
|
||||
var extent = d3.extent(self.xValues());
|
||||
if (missingMin) d.ordered.min = extent[0];
|
||||
if (missingMax) d.ordered.max = extent[1];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ module.exports = function (grunt) {
|
|||
grunt.registerTask('_build:babelOptions', function () {
|
||||
unlink(srcFile);
|
||||
rename(buildFile, srcFile);
|
||||
grunt.file.mkdir('build/kibana/optimize');
|
||||
});
|
||||
|
||||
};
|
||||
|
|
|
@ -44,7 +44,7 @@ module.exports = function (grunt) {
|
|||
purge: true,
|
||||
config: {
|
||||
http: {
|
||||
port: uiConfig.elasticsearch.port
|
||||
port: uiConfig.servers.elasticsearch.port
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,9 @@ module.exports = function (grunt) {
|
|||
},
|
||||
cmd: /^win/.test(platform) ? '.\\bin\\kibana.bat' : './bin/kibana',
|
||||
args: [
|
||||
'--server.port=' + uiConfig.kibana.port,
|
||||
'--elasticsearch.url=' + format(uiConfig.elasticsearch),
|
||||
'--server.port=' + uiConfig.servers.kibana.port,
|
||||
'--env.name=development',
|
||||
'--elasticsearch.url=' + format(uiConfig.servers.elasticsearch),
|
||||
'--logging.json=false'
|
||||
]
|
||||
},
|
||||
|
@ -89,7 +90,7 @@ module.exports = function (grunt) {
|
|||
'-jar',
|
||||
'selenium/selenium-server-standalone-2.47.1.jar',
|
||||
'-port',
|
||||
uiConfig.webdriver.port
|
||||
uiConfig.servers.webdriver.port
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -105,7 +106,7 @@ module.exports = function (grunt) {
|
|||
'-jar',
|
||||
'selenium/selenium-server-standalone-2.47.1.jar',
|
||||
'-port',
|
||||
uiConfig.webdriver.port
|
||||
uiConfig.servers.webdriver.port
|
||||
]
|
||||
},
|
||||
|
||||
|
|
92
test/fixtures/__tests__/scenarioManager.js
vendored
92
test/fixtures/__tests__/scenarioManager.js
vendored
|
@ -21,11 +21,11 @@ describe('scenario manager', function () {
|
|||
|
||||
it('should be able to load scenarios', function () {
|
||||
return manager.load('makelogs')
|
||||
.then(function () {
|
||||
expect(create.getCall(0).args[0].index).to.be('logstash-2015.09.17');
|
||||
expect(create.getCall(1).args[0].index).to.be('logstash-2015.09.18');
|
||||
expect(bulk.called).to.be(true);
|
||||
});
|
||||
.then(function () {
|
||||
expect(create.getCall(0).args[0].index).to.be('logstash-2015.09.17');
|
||||
expect(create.getCall(1).args[0].index).to.be('logstash-2015.09.18');
|
||||
expect(bulk.called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to delete all indices', function () {
|
||||
|
@ -55,6 +55,52 @@ describe('scenario manager', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should load if the index does not exist', function () {
|
||||
var load = sinon.stub(manager, 'load', Promise.resolve);
|
||||
var throwError = sinon.stub(manager.client, 'count', Promise.reject);
|
||||
var id = 'makelogs';
|
||||
return manager.loadIfEmpty(id).then(function () {
|
||||
expect(load.calledWith(id)).to.be(true);
|
||||
|
||||
load.restore();
|
||||
throwError.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('should load if the index is empty', function () {
|
||||
var load = sinon.stub(manager, 'load', Promise.resolve);
|
||||
var returnZero = sinon.stub(manager.client, 'count', function () {
|
||||
return Promise.resolve({
|
||||
'count': 0
|
||||
});
|
||||
});
|
||||
var id = 'makelogs';
|
||||
return manager.loadIfEmpty(id).then(function () {
|
||||
expect(load.calledWith(id)).to.be(true);
|
||||
|
||||
load.restore();
|
||||
returnZero.restore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not load if the index is not empty', function () {
|
||||
var load = sinon.stub(manager, 'load', Promise.resolve);
|
||||
var returnOne = sinon.stub(manager.client, 'count', function () {
|
||||
return Promise.resolve({
|
||||
'count': 1
|
||||
});
|
||||
});
|
||||
var id = 'makelogs';
|
||||
return manager.loadIfEmpty(id).then(function () {
|
||||
expect(load.called).to.be(false);
|
||||
|
||||
load.restore();
|
||||
returnOne.restore();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
afterEach(function () {
|
||||
bulk.restore();
|
||||
create.restore();
|
||||
|
@ -62,12 +108,40 @@ describe('scenario manager', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should throw an error if the scenario is not defined', function () {
|
||||
expect(manager.load).withArgs('makelogs').to.throwError();
|
||||
describe('load', function () {
|
||||
it('should reject if the scenario is not specified', function () {
|
||||
return manager.load()
|
||||
.then(function () {
|
||||
throw new Error('Promise should reject');
|
||||
})
|
||||
.catch(function () { return; });
|
||||
});
|
||||
|
||||
it('should reject if the scenario is not defined', function () {
|
||||
return manager.load('idonotexist')
|
||||
.then(function () {
|
||||
throw new Error('Promise should reject');
|
||||
})
|
||||
.catch(function () { return; });
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if an index is not defined when clearing', function () {
|
||||
expect(manager.unload).to.throwError();
|
||||
describe('unload', function () {
|
||||
it('should reject if the scenario is not specified', function () {
|
||||
return manager.unload()
|
||||
.then(function () {
|
||||
throw new Error('Promise should reject');
|
||||
})
|
||||
.catch(function () { return; });
|
||||
});
|
||||
|
||||
it('should reject if the scenario is not defined', function () {
|
||||
return manager.unload('idonotexist')
|
||||
.then(function () {
|
||||
throw new Error('Promise should reject');
|
||||
})
|
||||
.catch(function () { return; });
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if an es server is not specified', function () {
|
||||
|
|
40
test/fixtures/config.js
vendored
40
test/fixtures/config.js
vendored
|
@ -2,23 +2,27 @@ var path = require('path');
|
|||
var rootDir = path.join(__dirname, 'scenarios');
|
||||
|
||||
module.exports = {
|
||||
makelogs: {
|
||||
baseDir: path.join(rootDir, 'makelogs'),
|
||||
bulk: [{
|
||||
indexDefinition: 'makelogsIndexDefinition.js',
|
||||
indexName: 'logstash-2015.09.17',
|
||||
source: 'logstash-2015.09.17.js'
|
||||
}, {
|
||||
indexDefinition: 'makelogsIndexDefinition.js',
|
||||
indexName: 'logstash-2015.09.18',
|
||||
source: 'logstash-2015.09.18.js'
|
||||
}]
|
||||
},
|
||||
emptyKibana: {
|
||||
baseDir: path.join(rootDir, 'emptyKibana'),
|
||||
bulk: [{
|
||||
indexName: '.kibana',
|
||||
source: 'kibana.js'
|
||||
}]
|
||||
scenarios: {
|
||||
makelogs: {
|
||||
baseDir: path.join(rootDir, 'makelogs'),
|
||||
bulk: [{
|
||||
indexName: 'logstash-2015.09.17',
|
||||
indexDefinition: 'makelogsIndexDefinition.js',
|
||||
source: 'logstash-2015.09.17.js'
|
||||
}, {
|
||||
indexName: 'logstash-2015.09.18',
|
||||
indexDefinition: 'makelogsIndexDefinition.js',
|
||||
source: 'logstash-2015.09.18.js'
|
||||
}]
|
||||
},
|
||||
emptyKibana: {
|
||||
baseDir: path.join(rootDir, 'emptyKibana'),
|
||||
bulk: [{
|
||||
indexName: '.kibana',
|
||||
indexDefinition: 'kibanaDefinition.js',
|
||||
source: 'kibana.js',
|
||||
haltOnFailure: false
|
||||
}]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
61
test/fixtures/scenarioManager.js
vendored
61
test/fixtures/scenarioManager.js
vendored
|
@ -1,6 +1,7 @@
|
|||
var path = require('path');
|
||||
var config = require('./config');
|
||||
var elasticsearch = require('elasticsearch');
|
||||
var Promise = require('bluebird');
|
||||
var config = require('./config').scenarios;
|
||||
|
||||
function ScenarioManager(server) {
|
||||
if (!server) throw new Error('No server defined');
|
||||
|
@ -16,28 +17,33 @@ function ScenarioManager(server) {
|
|||
* @return {Promise} A promise that is resolved when elasticsearch has a response
|
||||
*/
|
||||
ScenarioManager.prototype.load = function (id) {
|
||||
var scenario = config[id];
|
||||
if (!scenario) throw new Error('No scenario found for ' + id);
|
||||
|
||||
var self = this;
|
||||
var scenario = config[id];
|
||||
if (!scenario) return Promise.reject('No scenario found for ' + id);
|
||||
|
||||
return Promise.all(scenario.bulk.map(function mapBulk(bulk) {
|
||||
var loadIndexDefinition;
|
||||
|
||||
if (bulk.indexDefinition) {
|
||||
var body = require(path.join(scenario.baseDir, bulk.indexDefinition));
|
||||
loadIndexDefinition = self.client.indices.create({
|
||||
index: bulk.indexName,
|
||||
body: require(path.join(scenario.baseDir, bulk.indexDefinition))
|
||||
body: body
|
||||
});
|
||||
} else {
|
||||
loadIndexDefinition = Promise.resolve();
|
||||
}
|
||||
|
||||
return loadIndexDefinition.then(function bulkRequest() {
|
||||
self.client.bulk({
|
||||
body: require(path.join(scenario.baseDir, bulk.source)),
|
||||
return loadIndexDefinition
|
||||
.then(function bulkRequest() {
|
||||
var body = require(path.join(scenario.baseDir, bulk.source));
|
||||
return self.client.bulk({
|
||||
body: body
|
||||
});
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (bulk.haltOnFailure === false) return;
|
||||
throw err;
|
||||
});
|
||||
|
||||
}));
|
||||
};
|
||||
|
||||
|
@ -48,7 +54,7 @@ ScenarioManager.prototype.load = function (id) {
|
|||
*/
|
||||
ScenarioManager.prototype.unload = function (id) {
|
||||
var scenario = config[id];
|
||||
if (!scenario) throw new Error('Expected index');
|
||||
if (!scenario) return Promise.reject('No scenario found for ' + id);
|
||||
|
||||
var indices = scenario.bulk.map(function mapBulk(bulk) {
|
||||
return bulk.indexName;
|
||||
|
@ -67,7 +73,8 @@ ScenarioManager.prototype.unload = function (id) {
|
|||
ScenarioManager.prototype.reload = function (id) {
|
||||
var self = this;
|
||||
|
||||
return this.unload(id).then(function load() {
|
||||
return self.unload(id)
|
||||
.then(function load() {
|
||||
return self.load(id);
|
||||
});
|
||||
};
|
||||
|
@ -82,4 +89,32 @@ ScenarioManager.prototype.deleteAll = function () {
|
|||
});
|
||||
};
|
||||
|
||||
module.exports = ScenarioManager;
|
||||
/**
|
||||
* Load a testing scenario if not already loaded
|
||||
* @param {string} id The scenario id to load
|
||||
* @return {Promise} A promise that is resolved when elasticsearch has a response
|
||||
*/
|
||||
ScenarioManager.prototype.loadIfEmpty = function (id) {
|
||||
var self = this;
|
||||
var scenario = config[id];
|
||||
if (!scenario) throw new Error('No scenario found for ' + id);
|
||||
|
||||
var self = this;
|
||||
return Promise.all(scenario.bulk.map(function mapBulk(bulk) {
|
||||
var loadIndexDefinition;
|
||||
|
||||
return self.client.count({
|
||||
index: bulk.indexName
|
||||
})
|
||||
.then(function handleCountResponse(response) {
|
||||
if (response.count === 0) {
|
||||
return self.load(id);
|
||||
}
|
||||
});
|
||||
}))
|
||||
.catch(function (reason) {
|
||||
return self.load(id);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = ScenarioManager;
|
16
test/fixtures/scenarios/emptyKibana/kibanaDefinition.js
vendored
Normal file
16
test/fixtures/scenarios/emptyKibana/kibanaDefinition.js
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
module.exports = {
|
||||
settings: {
|
||||
number_of_shards: 1,
|
||||
number_of_replicas: 1
|
||||
},
|
||||
mappings: {
|
||||
config: {
|
||||
properties: {
|
||||
buildNum: {
|
||||
type: 'string',
|
||||
index: 'not_analyzed'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
60
test/functional/apps/settings/_creation_form_changes.js
Normal file
60
test/functional/apps/settings/_creation_form_changes.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
define(function (require) {
|
||||
var Common = require('../../../support/pages/Common');
|
||||
var SettingsPage = require('../../../support/pages/SettingsPage');
|
||||
var expect = require('intern/dojo/node!expect.js');
|
||||
|
||||
return function (bdd, scenarioManager) {
|
||||
bdd.describe('user input reactions', function () {
|
||||
var common;
|
||||
var settingsPage;
|
||||
|
||||
bdd.before(function () {
|
||||
common = new Common(this.remote);
|
||||
settingsPage = new SettingsPage(this.remote);
|
||||
});
|
||||
|
||||
bdd.beforeEach(function () {
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
return settingsPage.navigateTo();
|
||||
});
|
||||
});
|
||||
|
||||
bdd.it('should hide time-based index pattern when time-based option is unchecked', function () {
|
||||
var self = this;
|
||||
return settingsPage.getTimeBasedEventsCheckbox()
|
||||
.then(function (selected) {
|
||||
// uncheck the 'time-based events' checkbox
|
||||
return selected.click();
|
||||
})
|
||||
// try to find the checkbox (this shouldn fail)
|
||||
.then(function () {
|
||||
var waitTime = 10000;
|
||||
return settingsPage.getTimeBasedIndexPatternCheckbox(waitTime);
|
||||
})
|
||||
.then(function () {
|
||||
// we expect the promise above to fail
|
||||
var handler = common.handleError(self);
|
||||
var msg = 'Found time based index pattern checkbox';
|
||||
handler(msg);
|
||||
})
|
||||
.catch(function () {
|
||||
// we expect this failure since checkbox should be hidden
|
||||
return;
|
||||
});
|
||||
});
|
||||
|
||||
bdd.it('should enable creation after selecting time field', function () {
|
||||
// select a time field and check that Create button is enabled
|
||||
return settingsPage.selectTimeFieldOption('@timestamp')
|
||||
.then(function () {
|
||||
return settingsPage.getCreateButton().isEnabled()
|
||||
.then(function (enabled) {
|
||||
expect(enabled).to.be.ok();
|
||||
});
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
104
test/functional/apps/settings/_index_pattern_create_delete.js
Normal file
104
test/functional/apps/settings/_index_pattern_create_delete.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
define(function (require) {
|
||||
var Common = require('../../../support/pages/Common');
|
||||
var SettingsPage = require('../../../support/pages/SettingsPage');
|
||||
var expect = require('intern/dojo/node!expect.js');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
return function (bdd, scenarioManager) {
|
||||
bdd.describe('creating and deleting default index', function describeIndexTests() {
|
||||
var common;
|
||||
var settingsPage;
|
||||
var remote;
|
||||
|
||||
bdd.before(function () {
|
||||
common = new Common(this.remote);
|
||||
settingsPage = new SettingsPage(this.remote);
|
||||
remote = this.remote;
|
||||
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
return settingsPage.navigateTo();
|
||||
});
|
||||
});
|
||||
|
||||
bdd.describe('index pattern creation', function indexPatternCreation() {
|
||||
bdd.before(function () {
|
||||
return settingsPage.createIndexPattern();
|
||||
});
|
||||
|
||||
bdd.it('should have index pattern in page header', function pageHeader() {
|
||||
return settingsPage.getIndexPageHeading().getVisibleText()
|
||||
.then(function (patternName) {
|
||||
expect(patternName).to.be('logstash-*');
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should have index pattern in url', function url() {
|
||||
return common.tryForTime(5000, function () {
|
||||
return remote.getCurrentUrl()
|
||||
.then(function (currentUrl) {
|
||||
expect(currentUrl).to.contain('logstash-*');
|
||||
});
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should have expected table headers', function checkingHeader() {
|
||||
return settingsPage.getTableHeader()
|
||||
.then(function (headers) {
|
||||
var expectedHeaders = [
|
||||
'name',
|
||||
'type',
|
||||
'format',
|
||||
'analyzed',
|
||||
'indexed',
|
||||
'controls'
|
||||
];
|
||||
|
||||
// 6 name type format analyzed indexed controls
|
||||
expect(headers.length).to.be(expectedHeaders.length);
|
||||
|
||||
var comparedHeaders = headers.map(function compareHead(header, i) {
|
||||
return header.getVisibleText()
|
||||
.then(function (text) {
|
||||
expect(text).to.be(expectedHeaders[i]);
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(comparedHeaders);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
});
|
||||
|
||||
bdd.describe('index pattern deletion', function indexDelete() {
|
||||
bdd.before(function () {
|
||||
var expectedAlertText = 'Are you sure you want to remove this index pattern?';
|
||||
return settingsPage.removeIndexPattern()
|
||||
.then(function (alertText) {
|
||||
expect(alertText).to.be(expectedAlertText);
|
||||
});
|
||||
});
|
||||
|
||||
bdd.it('should return to index pattern creation page', function returnToPage() {
|
||||
return common.tryForTime(5000, function () {
|
||||
return settingsPage.getCreateButton();
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should remove index pattern from url', function indexNotInUrl() {
|
||||
// give the url time to settle
|
||||
return common.tryForTime(5000, function () {
|
||||
return remote.getCurrentUrl()
|
||||
.then(function (currentUrl) {
|
||||
expect(currentUrl).to.not.contain('logstash-*');
|
||||
});
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
111
test/functional/apps/settings/_index_pattern_popularity.js
Normal file
111
test/functional/apps/settings/_index_pattern_popularity.js
Normal file
|
@ -0,0 +1,111 @@
|
|||
define(function (require) {
|
||||
var Common = require('../../../support/pages/Common');
|
||||
var SettingsPage = require('../../../support/pages/SettingsPage');
|
||||
var expect = require('intern/dojo/node!expect.js');
|
||||
//var Promise = require('bluebird');
|
||||
|
||||
return function (bdd, scenarioManager) {
|
||||
bdd.describe('index result popularity', function describeIndexTests() {
|
||||
var common;
|
||||
var settingsPage;
|
||||
var remote;
|
||||
|
||||
bdd.before(function () {
|
||||
common = new Common(this.remote);
|
||||
settingsPage = new SettingsPage(this.remote);
|
||||
remote = this.remote;
|
||||
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
return settingsPage.navigateTo();
|
||||
});
|
||||
});
|
||||
|
||||
bdd.beforeEach(function be() {
|
||||
return settingsPage.createIndexPattern();
|
||||
});
|
||||
|
||||
bdd.afterEach(function ae() {
|
||||
return settingsPage.removeIndexPattern();
|
||||
});
|
||||
|
||||
bdd.describe('change popularity', function indexPatternCreation() {
|
||||
var fieldName = 'geo.coordinates';
|
||||
|
||||
// set the page size to All again, https://github.com/elastic/kibana/issues/5030
|
||||
// TODO: remove this after issue #5030 is closed
|
||||
function fix5030() {
|
||||
return settingsPage.setPageSize('All')
|
||||
.then(function () {
|
||||
return common.sleep(1000);
|
||||
});
|
||||
}
|
||||
|
||||
bdd.beforeEach(function () {
|
||||
// increase Popularity of geo.coordinates
|
||||
return settingsPage.setPageSize('All')
|
||||
.then(function () {
|
||||
return common.sleep(1000);
|
||||
})
|
||||
.then(function openControlsByName() {
|
||||
return settingsPage.openControlsByName(fieldName);
|
||||
})
|
||||
.then(function increasePopularity() {
|
||||
return settingsPage.increasePopularity();
|
||||
});
|
||||
});
|
||||
|
||||
bdd.afterEach(function () {
|
||||
// Cancel saving the popularity change (we didn't make a change in this case, just checking the value)
|
||||
return settingsPage.controlChangeCancel();
|
||||
});
|
||||
|
||||
bdd.it('should update the popularity input', function () {
|
||||
return settingsPage.getPopularity()
|
||||
.then(function (popularity) {
|
||||
expect(popularity).to.be('1');
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should be reset on cancel', function pageHeader() {
|
||||
// Cancel saving the popularity change
|
||||
return settingsPage.controlChangeCancel()
|
||||
.then(function () {
|
||||
return fix5030();
|
||||
})
|
||||
.then(function openControlsByName() {
|
||||
return settingsPage.openControlsByName(fieldName);
|
||||
})
|
||||
// check that its 0 (previous increase was cancelled)
|
||||
.then(function getPopularity() {
|
||||
return settingsPage.getPopularity();
|
||||
})
|
||||
.then(function (popularity) {
|
||||
expect(popularity).to.be('0');
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('can be saved', function pageHeader() {
|
||||
// Saving the popularity change
|
||||
return settingsPage.controlChangeSave()
|
||||
.then(function () {
|
||||
return fix5030();
|
||||
})
|
||||
.then(function openControlsByName() {
|
||||
return settingsPage.openControlsByName(fieldName);
|
||||
})
|
||||
// check that its 0 (previous increase was cancelled)
|
||||
.then(function getPopularity() {
|
||||
return settingsPage.getPopularity();
|
||||
})
|
||||
.then(function (popularity) {
|
||||
expect(popularity).to.be('1');
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
}); // end 'change popularity'
|
||||
}); // end index result popularity
|
||||
};
|
||||
});
|
134
test/functional/apps/settings/_index_pattern_results_sort.js
Normal file
134
test/functional/apps/settings/_index_pattern_results_sort.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
define(function (require) {
|
||||
var Common = require('../../../support/pages/Common');
|
||||
var SettingsPage = require('../../../support/pages/SettingsPage');
|
||||
var expect = require('intern/dojo/node!expect.js');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
return function (bdd, scenarioManager) {
|
||||
bdd.describe('index result field sort', function describeIndexTests() {
|
||||
var common;
|
||||
var settingsPage;
|
||||
var remote;
|
||||
|
||||
bdd.before(function () {
|
||||
common = new Common(this.remote);
|
||||
settingsPage = new SettingsPage(this.remote);
|
||||
remote = this.remote;
|
||||
|
||||
return scenarioManager.reload('emptyKibana');
|
||||
});
|
||||
|
||||
var columns = [{
|
||||
heading: 'name',
|
||||
first: '@message',
|
||||
last: 'xss.raw',
|
||||
selector: function () {
|
||||
return settingsPage.getTableRow(0, 0).getVisibleText();
|
||||
}
|
||||
}, {
|
||||
heading: 'type',
|
||||
first: '_source',
|
||||
last: 'string',
|
||||
selector: function () {
|
||||
return settingsPage.getTableRow(0, 1).getVisibleText();
|
||||
}
|
||||
}];
|
||||
|
||||
columns.forEach(function (col) {
|
||||
bdd.describe('sort by heading - ' + col.heading, function indexPatternCreation() {
|
||||
bdd.before(function () {
|
||||
return settingsPage.navigateTo();
|
||||
});
|
||||
|
||||
bdd.beforeEach(function () {
|
||||
return settingsPage.createIndexPattern();
|
||||
});
|
||||
|
||||
bdd.afterEach(function () {
|
||||
return settingsPage.removeIndexPattern();
|
||||
});
|
||||
|
||||
bdd.it('should sort ascending', function pageHeader() {
|
||||
return settingsPage.sortBy(col.heading)
|
||||
.then(function getText() {
|
||||
return col.selector();
|
||||
})
|
||||
.then(function (rowText) {
|
||||
expect(rowText).to.be(col.first);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should sort descending', function pageHeader() {
|
||||
return settingsPage.sortBy(col.heading)
|
||||
.then(function sortAgain() {
|
||||
return settingsPage.sortBy(col.heading);
|
||||
})
|
||||
.then(function getText() {
|
||||
return col.selector();
|
||||
})
|
||||
.then(function (rowText) {
|
||||
expect(rowText).to.be(col.last);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
bdd.describe('field list pagination', function () {
|
||||
var expectedDefaultPageSize = 25;
|
||||
var expectedFieldCount = 85;
|
||||
var expectedLastPageCount = 10;
|
||||
var pages = [1, 2, 3, 4];
|
||||
|
||||
bdd.before(function () {
|
||||
return settingsPage.navigateTo()
|
||||
.then(function () {
|
||||
return settingsPage.createIndexPattern();
|
||||
});
|
||||
});
|
||||
|
||||
bdd.after(function () {
|
||||
return settingsPage.removeIndexPattern();
|
||||
});
|
||||
|
||||
bdd.it('makelogs data should have expected number of fields', function () {
|
||||
return settingsPage.getFieldsTabCount()
|
||||
.then(function (tabCount) {
|
||||
expect(tabCount).to.be('' + expectedFieldCount);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should have correct default page size selected', function () {
|
||||
return settingsPage.getPageSize()
|
||||
.then(function (pageSize) {
|
||||
expect(pageSize).to.be('' + expectedDefaultPageSize);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should have the correct number of rows per page', function () {
|
||||
var pageCount = Math.ceil(expectedFieldCount / expectedDefaultPageSize);
|
||||
var chain = pages.reduce(function (chain, val) {
|
||||
return chain.then(function () {
|
||||
return settingsPage.goToPage(val)
|
||||
.then(function () {
|
||||
return common.sleep(1000);
|
||||
})
|
||||
.then(function () {
|
||||
return settingsPage.getPageFieldCount();
|
||||
})
|
||||
.then(function (pageCount) {
|
||||
var expectedSize = (val < 4) ? expectedDefaultPageSize : expectedLastPageCount;
|
||||
expect(pageCount.length).to.be(expectedSize);
|
||||
});
|
||||
});
|
||||
}, Promise.resolve());
|
||||
|
||||
return chain.catch(common.handleError(this));
|
||||
});
|
||||
}); // end describe pagination
|
||||
}); // end index result field sort
|
||||
};
|
||||
});
|
64
test/functional/apps/settings/_initial_state.js
Normal file
64
test/functional/apps/settings/_initial_state.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
define(function (require) {
|
||||
var expect = require('intern/dojo/node!expect.js');
|
||||
var Common = require('../../../support/pages/Common');
|
||||
var SettingsPage = require('../../../support/pages/SettingsPage');
|
||||
|
||||
return function (bdd, scenarioManager) {
|
||||
bdd.describe('initial state', function () {
|
||||
var common;
|
||||
var settingsPage;
|
||||
|
||||
bdd.before(function () {
|
||||
common = new Common(this.remote);
|
||||
settingsPage = new SettingsPage(this.remote);
|
||||
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
return settingsPage.navigateTo();
|
||||
});
|
||||
});
|
||||
|
||||
bdd.it('should load with time pattern checked', function () {
|
||||
return settingsPage.getTimeBasedEventsCheckbox().isSelected()
|
||||
.then(function (selected) {
|
||||
expect(selected).to.be.ok();
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should load with name pattern unchecked', function () {
|
||||
return settingsPage.getTimeBasedIndexPatternCheckbox().isSelected()
|
||||
.then(function (selected) {
|
||||
expect(selected).to.not.be.ok();
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should contain default index pattern', function () {
|
||||
var defaultPattern = 'logstash-*';
|
||||
|
||||
return settingsPage.getIndexPatternField().getProperty('value')
|
||||
.then(function (pattern) {
|
||||
expect(pattern).to.be(defaultPattern);
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should not select the time field', function () {
|
||||
return settingsPage.getTimeFieldNameField().isSelected()
|
||||
.then(function (timeFieldIsSelected) {
|
||||
expect(timeFieldIsSelected).to.not.be.ok();
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
|
||||
bdd.it('should not be enable creation', function () {
|
||||
return settingsPage.getCreateButton().isEnabled()
|
||||
.then(function (enabled) {
|
||||
expect(enabled).to.not.be.ok();
|
||||
})
|
||||
.catch(common.handleError(this));
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
38
test/functional/apps/settings/index.js
Normal file
38
test/functional/apps/settings/index.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
define(function (require) {
|
||||
var bdd = require('intern!bdd');
|
||||
var config = require('intern').config;
|
||||
var url = require('intern/dojo/node!url');
|
||||
var ScenarioManager = require('intern/dojo/node!../../../fixtures/scenarioManager');
|
||||
|
||||
var initialStateTest = require('./_initial_state');
|
||||
var creationChangesTest = require('./_creation_form_changes');
|
||||
var indexPatternCreateDeleteTest = require('./_index_pattern_create_delete');
|
||||
var indexPatternResultsSortTest = require('./_index_pattern_results_sort');
|
||||
var indexPatternPopularityTest = require('./_index_pattern_popularity');
|
||||
|
||||
bdd.describe('settings app', function () {
|
||||
var scenarioManager = new ScenarioManager(url.format(config.servers.elasticsearch));
|
||||
|
||||
// on setup, we create an settingsPage instance
|
||||
// that we will use for all the tests
|
||||
bdd.before(function () {
|
||||
return scenarioManager.reload('emptyKibana')
|
||||
.then(function () {
|
||||
return scenarioManager.loadIfEmpty('makelogs');
|
||||
});
|
||||
});
|
||||
|
||||
bdd.after(function () {
|
||||
return scenarioManager.unload('makelogs')
|
||||
.then(function () {
|
||||
scenarioManager.unload('emptyKibana');
|
||||
});
|
||||
});
|
||||
|
||||
initialStateTest(bdd, scenarioManager);
|
||||
creationChangesTest(bdd, scenarioManager);
|
||||
indexPatternCreateDeleteTest(bdd, scenarioManager);
|
||||
indexPatternResultsSortTest(bdd, scenarioManager);
|
||||
indexPatternPopularityTest(bdd, scenarioManager);
|
||||
});
|
||||
});
|
|
@ -1,21 +0,0 @@
|
|||
define(function (require) {
|
||||
var registerSuite = require('intern!object');
|
||||
var expect = require('intern/dojo/node!expect.js');
|
||||
var config = require('intern').config;
|
||||
var getUrl = require('intern/dojo/node!../utils/getUrl');
|
||||
|
||||
registerSuite(function () {
|
||||
return {
|
||||
'status': function () {
|
||||
return this.remote
|
||||
.get(getUrl(config.kibana, 'status'))
|
||||
.setFindTimeout(60000)
|
||||
.findByCssSelector('.plugin_status_breakdown')
|
||||
.getVisibleText()
|
||||
.then(function (text) {
|
||||
expect(text.indexOf('plugin:kibana Ready')).to.be.above(-1);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
30
test/functional/status_page/index.js
Normal file
30
test/functional/status_page/index.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
define(function (require) {
|
||||
var bdd = require('intern!bdd');
|
||||
var expect = require('intern/dojo/node!expect.js');
|
||||
var config = require('intern').config;
|
||||
var Common = require('../../support/pages/Common');
|
||||
|
||||
bdd.describe('status page', function () {
|
||||
var common;
|
||||
|
||||
bdd.before(function () {
|
||||
common = new Common(this.remote);
|
||||
// load the status page
|
||||
return common.navigateToApp('statusPage', false);
|
||||
});
|
||||
|
||||
bdd.it('should show the kibana plugin as ready', function () {
|
||||
var self = this;
|
||||
|
||||
return common.tryForTime(6000, function () {
|
||||
return self.remote
|
||||
.findByCssSelector('.plugin_status_breakdown')
|
||||
.getVisibleText()
|
||||
.then(function (text) {
|
||||
expect(text.indexOf('plugin:kibana Ready')).to.be.above(-1);
|
||||
});
|
||||
})
|
||||
.catch(common.handleError(self));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -3,6 +3,7 @@ define(function (require) {
|
|||
var _ = require('intern/dojo/node!lodash');
|
||||
|
||||
return _.assign({
|
||||
debug: false,
|
||||
capabilities: {
|
||||
'selenium-version': '2.47.1',
|
||||
'idle-timeout': 30
|
||||
|
@ -10,8 +11,17 @@ define(function (require) {
|
|||
environments: [{
|
||||
browserName: 'firefox'
|
||||
}],
|
||||
tunnelOptions: serverConfig.webdriver,
|
||||
functionalSuites: ['test/functional/status.js'],
|
||||
excludeInstrumentation: /(fixtures|node_modules)\//
|
||||
tunnelOptions: serverConfig.servers.webdriver,
|
||||
functionalSuites: [
|
||||
'test/functional/status_page/index',
|
||||
'test/functional/apps/settings/index'
|
||||
],
|
||||
excludeInstrumentation: /(fixtures|node_modules)\//,
|
||||
loaderOptions: {
|
||||
paths: {
|
||||
'bluebird': './node_modules/bluebird/js/browser/bluebird.js',
|
||||
'moment': './node_modules/moment/moment.js'
|
||||
}
|
||||
}
|
||||
}, serverConfig);
|
||||
});
|
||||
|
|
|
@ -1,17 +1,42 @@
|
|||
var kibanaURL = '/app/kibana';
|
||||
|
||||
module.exports = {
|
||||
webdriver: {
|
||||
protocol: process.env.TEST_UI_WEBDRIVER_PROTOCOL || 'http',
|
||||
hostname: process.env.TEST_UI_WEBDRIVER_HOSTNAME || 'localhost',
|
||||
port: parseInt(process.env.TEST_UI_WEBDRIVER_PORT, 10) || 4444
|
||||
servers: {
|
||||
webdriver: {
|
||||
protocol: process.env.TEST_UI_WEBDRIVER_PROTOCOL || 'http',
|
||||
hostname: process.env.TEST_UI_WEBDRIVER_HOSTNAME || 'localhost',
|
||||
port: parseInt(process.env.TEST_UI_WEBDRIVER_PORT, 10) || 4444
|
||||
},
|
||||
kibana: {
|
||||
protocol: process.env.TEST_UI_KIBANA_PROTOCOL || 'http',
|
||||
hostname: process.env.TEST_UI_KIBANA_HOSTNAME || 'localhost',
|
||||
port: parseInt(process.env.TEST_UI_KIBANA_PORT, 10) || 5620
|
||||
},
|
||||
elasticsearch: {
|
||||
protocol: process.env.TEST_UI_ES_PROTOCOL || 'http',
|
||||
hostname: process.env.TEST_UI_ES_HOSTNAME || 'localhost',
|
||||
port: parseInt(process.env.TEST_UI_ES_PORT, 10) || 9220
|
||||
}
|
||||
},
|
||||
kibana: {
|
||||
protocol: process.env.TEST_UI_KIBANA_PROTOCOL || 'http',
|
||||
hostname: process.env.TEST_UI_KIBANA_HOSTNAME || 'localhost',
|
||||
port: parseInt(process.env.TEST_UI_KIBANA_PORT, 10) || 5620
|
||||
},
|
||||
elasticsearch: {
|
||||
protocol: process.env.TEST_UI_ES_PROTOCOL || 'http',
|
||||
hostname: process.env.TEST_UI_ES_HOSTNAME || 'localhost',
|
||||
port: parseInt(process.env.TEST_UI_ES_PORT, 10) || 9220
|
||||
apps: {
|
||||
statusPage: {
|
||||
pathname: 'status'
|
||||
},
|
||||
discover: {
|
||||
pathname: kibanaURL,
|
||||
hash: '/discover',
|
||||
},
|
||||
visualize: {
|
||||
pathname: kibanaURL,
|
||||
hash: '/visualize',
|
||||
},
|
||||
dashboard: {
|
||||
pathname: kibanaURL,
|
||||
hash: '/dashboard',
|
||||
},
|
||||
settings: {
|
||||
pathname: kibanaURL,
|
||||
hash: '/settings'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
198
test/support/pages/Common.js
Normal file
198
test/support/pages/Common.js
Normal file
|
@ -0,0 +1,198 @@
|
|||
// in test/support/pages/Common.js
|
||||
define(function (require) {
|
||||
var config = require('intern').config;
|
||||
var Promise = require('bluebird');
|
||||
var moment = require('moment');
|
||||
var getUrl = require('intern/dojo/node!../../utils/getUrl');
|
||||
var fs = require('intern/dojo/node!fs');
|
||||
var path = require('intern/dojo/node!path');
|
||||
|
||||
function Common(remote) {
|
||||
this.remote = remote;
|
||||
}
|
||||
|
||||
var defaultTimeout = 60000;
|
||||
|
||||
Common.prototype = {
|
||||
constructor: Common,
|
||||
|
||||
navigateToApp: function (appName, testStatusPage) {
|
||||
var self = this;
|
||||
var appUrl = getUrl(config.servers.kibana, config.apps[appName]);
|
||||
|
||||
var doNavigation = function (url) {
|
||||
return self.tryForTime(defaultTimeout, function () {
|
||||
// since we're using hash URLs, always reload first to force re-render
|
||||
return self.remote.get(url)
|
||||
.then(function () {
|
||||
return self.remote.refresh();
|
||||
})
|
||||
.then(function () {
|
||||
if (testStatusPage !== false) {
|
||||
return self.checkForKibanaApp()
|
||||
.then(function (kibanaLoaded) {
|
||||
if (!kibanaLoaded) throw new Error('Kibana is not loaded, retrying');
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
return self.remote.getCurrentUrl();
|
||||
})
|
||||
.then(function (currentUrl) {
|
||||
var navSuccessful = new RegExp(appUrl).test(currentUrl);
|
||||
if (!navSuccessful) throw new Error('App failed to load: ' + appName);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return doNavigation(appUrl)
|
||||
.then(function () {
|
||||
return self.remote.getCurrentUrl();
|
||||
})
|
||||
.then(function (currentUrl) {
|
||||
var lastUrl = currentUrl;
|
||||
return self.tryForTime(defaultTimeout, function () {
|
||||
// give the app time to update the URL
|
||||
return self.sleep(500)
|
||||
.then(function () {
|
||||
return self.remote.getCurrentUrl();
|
||||
})
|
||||
.then(function (currentUrl) {
|
||||
if (lastUrl !== currentUrl) {
|
||||
lastUrl = currentUrl;
|
||||
throw new Error('URL changed, waiting for it to settle');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
runScript: function (fn, timeout) {
|
||||
var self = this;
|
||||
// by default, give the app 10 seconds to load
|
||||
timeout = timeout || 10000;
|
||||
|
||||
// wait for deps on window before running script
|
||||
return self.remote
|
||||
.setExecuteAsyncTimeout(timeout)
|
||||
.executeAsync(function (done) {
|
||||
var interval = setInterval(function () {
|
||||
var ready = (document.readyState === 'complete');
|
||||
var hasJQuery = !!window.$;
|
||||
|
||||
if (ready && hasJQuery) {
|
||||
console.log('doc ready, jquery loaded');
|
||||
clearInterval(interval);
|
||||
done();
|
||||
}
|
||||
}, 10);
|
||||
}).then(function () {
|
||||
return self.remote.execute(fn);
|
||||
});
|
||||
},
|
||||
|
||||
getApp: function () {
|
||||
var self = this;
|
||||
|
||||
return self.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('.content > .application')
|
||||
.then(function () {
|
||||
return self.runScript(function () {
|
||||
var $ = window.$;
|
||||
var $scope = $('.content > .application').scope();
|
||||
return $scope ? $scope.chrome.getApp() : {};
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
checkForKibanaApp: function () {
|
||||
var self = this;
|
||||
|
||||
return self.getApp()
|
||||
.then(function (app) {
|
||||
var appId = app.id;
|
||||
self.debug('current application: ' + appId);
|
||||
return appId === 'kibana';
|
||||
})
|
||||
.catch(function (err) {
|
||||
self.debug('kibana check failed');
|
||||
self.debug(err);
|
||||
// not on the kibana app...
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
tryForTime: function (timeout, block) {
|
||||
var self = this;
|
||||
var start = Date.now();
|
||||
var retryDelay = 500;
|
||||
var lastTry = 0;
|
||||
|
||||
function attempt() {
|
||||
lastTry = Date.now();
|
||||
|
||||
if (lastTry - start > timeout) {
|
||||
throw new Error('timeout');
|
||||
}
|
||||
|
||||
return Promise
|
||||
.try(block)
|
||||
.then(function tryForTimeSuccess() {
|
||||
self.debug('tryForTime success in about ' + (lastTry - start) + ' ms');
|
||||
return (lastTry - start);
|
||||
})
|
||||
.catch(function tryForTimeCatch(err) {
|
||||
self.debug('tryForTime failure, retry in ' + retryDelay + 'ms - ' + err.message);
|
||||
return Promise.delay(retryDelay).then(attempt);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.try(attempt);
|
||||
},
|
||||
|
||||
log: function (logString) {
|
||||
console.log(moment().format('HH:mm:ss.SSS') + ': ' + logString);
|
||||
},
|
||||
|
||||
debug: function (logString) {
|
||||
if (config.debug) this.log(logString);
|
||||
},
|
||||
|
||||
sleep: function (sleepMilliseconds) {
|
||||
this.debug('sleeping for ' + sleepMilliseconds + 'ms');
|
||||
return Promise.resolve().delay(sleepMilliseconds);
|
||||
},
|
||||
|
||||
handleError: function (testObj) {
|
||||
var self = this;
|
||||
var testName = (testObj.parent) ? [testObj.parent.name, testObj.name].join('_') : testObj.name;
|
||||
|
||||
return function (reason) {
|
||||
var now = Date.now();
|
||||
var filename = ['failure', now, testName].join('_') + '.png';
|
||||
|
||||
return self.saveScreenshot(filename)
|
||||
.finally(function () {
|
||||
throw new Error(reason);
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
saveScreenshot: function (filename) {
|
||||
var self = this;
|
||||
var outDir = path.resolve('test', 'output');
|
||||
|
||||
return self.remote.takeScreenshot()
|
||||
.then(function writeScreenshot(data) {
|
||||
var filepath = path.resolve(outDir, filename);
|
||||
self.debug('Test Failed, taking screenshot "' + filepath + '"');
|
||||
fs.writeFileSync(filepath, data);
|
||||
})
|
||||
.catch(function (err) {
|
||||
self.log('SCREENSHOT FAILED: ' + err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return Common;
|
||||
});
|
54
test/support/pages/HeaderPage.js
Normal file
54
test/support/pages/HeaderPage.js
Normal file
|
@ -0,0 +1,54 @@
|
|||
// in test/support/pages/HeaderPage.js
|
||||
define(function (require) {
|
||||
|
||||
var Common = require('./Common');
|
||||
|
||||
var common;
|
||||
|
||||
// the page object is created as a constructor
|
||||
// so we can provide the remote Command object
|
||||
// at runtime
|
||||
function HeaderPage(remote) {
|
||||
this.remote = remote;
|
||||
common = new Common(this.remote);
|
||||
}
|
||||
|
||||
var defaultTimeout = 5000;
|
||||
|
||||
HeaderPage.prototype = {
|
||||
constructor: HeaderPage,
|
||||
|
||||
clickSelector: function (selector) {
|
||||
var self = this.remote;
|
||||
return common.tryForTime(5000, function () {
|
||||
return self.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector(selector)
|
||||
.then(function (tab) {
|
||||
return tab.click();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
clickDiscover: function () {
|
||||
common.debug('click Discover tab');
|
||||
this.clickSelector('a[href*=\'discover\']');
|
||||
},
|
||||
|
||||
clickVisualize: function () {
|
||||
common.debug('click Visualize tab');
|
||||
this.clickSelector('a[href*=\'visualize\']');
|
||||
},
|
||||
|
||||
clickDashboard: function () {
|
||||
common.debug('click Dashboard tab');
|
||||
this.clickSelector('a[href*=\'dashboard\']');
|
||||
},
|
||||
|
||||
clickSettings: function () {
|
||||
common.debug('click Settings tab');
|
||||
this.clickSelector('a[href*=\'settings\']');
|
||||
}
|
||||
};
|
||||
|
||||
return HeaderPage;
|
||||
});
|
299
test/support/pages/SettingsPage.js
Normal file
299
test/support/pages/SettingsPage.js
Normal file
|
@ -0,0 +1,299 @@
|
|||
// in test/support/pages/SettingsPage.js
|
||||
define(function (require) {
|
||||
// the page object is created as a constructor
|
||||
// so we can provide the remote Command object
|
||||
// at runtime
|
||||
|
||||
var Promise = require('bluebird');
|
||||
var Common = require('./Common');
|
||||
|
||||
var defaultTimeout = 60000;
|
||||
var common;
|
||||
|
||||
function SettingsPage(remote) {
|
||||
this.remote = remote;
|
||||
common = new Common(this.remote);
|
||||
}
|
||||
|
||||
SettingsPage.prototype = {
|
||||
constructor: SettingsPage,
|
||||
|
||||
navigateTo: function () {
|
||||
return common.navigateToApp('settings');
|
||||
},
|
||||
|
||||
getTimeBasedEventsCheckbox: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('input[ng-model="index.isTimeBased"]');
|
||||
},
|
||||
|
||||
getTimeBasedIndexPatternCheckbox: function (timeout) {
|
||||
timeout = timeout || defaultTimeout;
|
||||
// fail faster since we're sometimes checking that it doesn't exist
|
||||
return this.remote.setFindTimeout(timeout)
|
||||
.findByCssSelector('input[ng-model="index.nameIsPattern"]');
|
||||
},
|
||||
|
||||
getIndexPatternField: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('[ng-model="index.name"]');
|
||||
},
|
||||
|
||||
getTimeFieldNameField: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('select[ng-model="index.timeField"]');
|
||||
},
|
||||
|
||||
selectTimeFieldOption: function (selection) {
|
||||
var self = this;
|
||||
|
||||
return self.getTimeFieldNameField().click()
|
||||
.then(function () {
|
||||
return self.getTimeFieldNameField().click();
|
||||
})
|
||||
.then(function () {
|
||||
return self.getTimeFieldOption(selection);
|
||||
});
|
||||
},
|
||||
|
||||
getTimeFieldOption: function (selection) {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('option[label="' + selection + '"]').click();
|
||||
},
|
||||
|
||||
getCreateButton: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('.btn');
|
||||
},
|
||||
|
||||
clickCreateButton: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('.btn').click();
|
||||
},
|
||||
|
||||
clickDefaultIndexButton: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('button.btn.btn-warning.ng-scope').click();
|
||||
},
|
||||
|
||||
clickDeletePattern: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('button.btn.btn-danger.ng-scope').click();
|
||||
},
|
||||
|
||||
getIndexPageHeading: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('h1.title.ng-binding.ng-isolate-scope');
|
||||
},
|
||||
|
||||
getConfigureHeader: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('h1');
|
||||
},
|
||||
getTableHeader: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findAllByCssSelector('table.table.table-condensed thead tr th');
|
||||
},
|
||||
|
||||
sortBy: function (columnName) {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findAllByCssSelector('table.table.table-condensed thead tr th span')
|
||||
.then(function (chartTypes) {
|
||||
|
||||
function getChartType(chart) {
|
||||
return chart.getVisibleText()
|
||||
.then(function (chartString) {
|
||||
if (chartString === columnName) {
|
||||
return chart.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
var getChartTypesPromises = chartTypes.map(getChartType);
|
||||
return Promise.all(getChartTypesPromises);
|
||||
});
|
||||
},
|
||||
|
||||
getTableRow: function (rowNumber, colNumber) {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
// passing in zero-based index, but adding 1 for css 1-based indexes
|
||||
.findByCssSelector('div.agg-table-paginated table.table.table-condensed tbody tr:nth-child(' +
|
||||
(rowNumber + 1) + ') td.ng-scope:nth-child(' +
|
||||
(colNumber + 1) + ') span.ng-binding'
|
||||
);
|
||||
},
|
||||
|
||||
getFieldsTabCount: function () {
|
||||
var self = this;
|
||||
var selector = 'li.kbn-settings-tab.active a small';
|
||||
|
||||
var getText = function () {
|
||||
return self.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector(selector).getVisibleText();
|
||||
};
|
||||
|
||||
return common.tryForTime(defaultTimeout, function () {
|
||||
return getText();
|
||||
})
|
||||
.then(function () {
|
||||
return getText();
|
||||
})
|
||||
.then(function (theText) {
|
||||
// the value has () around it, remove them
|
||||
return theText.replace(/\((.*)\)/, '$1');
|
||||
});
|
||||
},
|
||||
|
||||
getPageSize: function () {
|
||||
var selectedItemLabel = '';
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findAllByCssSelector('select.ng-pristine.ng-valid.ng-untouched option')
|
||||
.then(function (chartTypes) {
|
||||
|
||||
function getChartType(chart) {
|
||||
var thisChart = chart;
|
||||
return chart.isSelected()
|
||||
.then(function (isSelected) {
|
||||
if (isSelected === true) {
|
||||
return thisChart.getProperty('label')
|
||||
.then(function (theLabel) {
|
||||
selectedItemLabel = theLabel;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
var getChartTypesPromises = chartTypes.map(getChartType);
|
||||
return Promise.all(getChartTypesPromises);
|
||||
})
|
||||
.then(function () {
|
||||
return selectedItemLabel;
|
||||
});
|
||||
},
|
||||
|
||||
getPageFieldCount: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findAllByCssSelector('div.agg-table-paginated table.table.table-condensed tbody tr td.ng-scope:nth-child(1) span.ng-binding');
|
||||
},
|
||||
|
||||
goToPage: function (pageNum) {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('ul.pagination-other-pages-list.pagination-sm.ng-scope li.ng-scope:nth-child(' +
|
||||
(pageNum + 1) + ') a.ng-binding'
|
||||
)
|
||||
.then(function (page) {
|
||||
return page.click();
|
||||
});
|
||||
},
|
||||
|
||||
openControlsRow: function (row) {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('table.table.table-condensed tbody tr:nth-child(' +
|
||||
(row + 1) + ') td.ng-scope div.actions a.btn.btn-xs.btn-default i.fa.fa-pencil'
|
||||
)
|
||||
.then(function (page) {
|
||||
return page.click();
|
||||
});
|
||||
},
|
||||
|
||||
openControlsByName: function (name) {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('div.actions a.btn.btn-xs.btn-default[href$="/' + name + '"]')
|
||||
.then(function (button) {
|
||||
return button.click();
|
||||
});
|
||||
},
|
||||
|
||||
increasePopularity: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('button.btn.btn-default[aria-label="Plus"]')
|
||||
.then(function (button) {
|
||||
return button.click();
|
||||
});
|
||||
},
|
||||
|
||||
getPopularity: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('input[ng-model="editor.field.count"]')
|
||||
.then(function (input) {
|
||||
return input.getProperty('value');
|
||||
});
|
||||
},
|
||||
|
||||
controlChangeCancel: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('button.btn.btn-primary[aria-label="Cancel"]')
|
||||
.then(function (button) {
|
||||
return button.click();
|
||||
});
|
||||
},
|
||||
|
||||
controlChangeSave: function () {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('button.btn.btn-success.ng-binding[aria-label="Update Field"]')
|
||||
.then(function (button) {
|
||||
return button.click();
|
||||
});
|
||||
},
|
||||
|
||||
setPageSize: function (size) {
|
||||
return this.remote.setFindTimeout(defaultTimeout)
|
||||
.findByCssSelector('form.form-inline.pagination-size.ng-scope.ng-pristine.ng-valid div.form-group option[label="' + size + '"]')
|
||||
.then(function (button) {
|
||||
return button.click();
|
||||
});
|
||||
},
|
||||
|
||||
createIndexPattern: function () {
|
||||
var self = this;
|
||||
|
||||
return common.tryForTime(defaultTimeout, function () {
|
||||
return self.selectTimeFieldOption('@timestamp')
|
||||
.then(function () {
|
||||
return self.getCreateButton().click();
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
return common.tryForTime(defaultTimeout, function () {
|
||||
return self.remote.getCurrentUrl()
|
||||
.then(function (currentUrl) {
|
||||
if (!currentUrl.match(/indices\/.+\?/)) {
|
||||
throw new Error('Index pattern not created');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
removeIndexPattern: function () {
|
||||
var self = this;
|
||||
var alertText;
|
||||
|
||||
return common.tryForTime(defaultTimeout, function () {
|
||||
return self.clickDeletePattern()
|
||||
.then(function () {
|
||||
return self.remote.getAlertText();
|
||||
})
|
||||
.then(function (text) {
|
||||
alertText = text;
|
||||
})
|
||||
.then(function () {
|
||||
return self.remote.acceptAlert();
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
return common.tryForTime(defaultTimeout, function () {
|
||||
return self.remote.getCurrentUrl()
|
||||
.then(function (currentUrl) {
|
||||
if (currentUrl.match(/indices\/.+\?/)) {
|
||||
throw new Error('Index pattern not removed');
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
return alertText;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return SettingsPage;
|
||||
});
|
|
@ -2,17 +2,36 @@ var expect = require('expect.js');
|
|||
var getUrl = require('../getUrl');
|
||||
|
||||
describe('getUrl', function () {
|
||||
it('should be able to convert a config and a path to a url', function () {
|
||||
expect(getUrl({
|
||||
it('should convert to a url', function () {
|
||||
var url = getUrl({
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
}, {
|
||||
pathname: 'foo'
|
||||
});
|
||||
|
||||
expect(url).to.be('http://localhost/foo');
|
||||
});
|
||||
|
||||
it('should convert to a secure url with port', function () {
|
||||
var url = getUrl({
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: 9220
|
||||
}, 'foo')).to.be('http://localhost:9220/foo');
|
||||
}, {
|
||||
pathname: 'foo'
|
||||
});
|
||||
|
||||
expect(url).to.be('http://localhost:9220/foo');
|
||||
});
|
||||
|
||||
it('should convert to a secure hashed url', function () {
|
||||
expect(getUrl({
|
||||
protocol: 'https',
|
||||
hostname: 'localhost',
|
||||
}, 'foo')).to.be('https://localhost/foo');
|
||||
|
||||
}, {
|
||||
pathname: 'foo',
|
||||
hash: 'bar'
|
||||
})).to.be('https://localhost/foo#bar');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
var _ = require('lodash');
|
||||
var url = require('url');
|
||||
|
||||
|
||||
/**
|
||||
* Converts a config and a pathname to a url
|
||||
* @param {object} config A url config
|
||||
|
@ -11,11 +10,14 @@ var url = require('url');
|
|||
* hostname: 'localhost',
|
||||
* port: 9220
|
||||
* }
|
||||
* @param {string} pathname The requested path
|
||||
* @param {object} app The params to append
|
||||
* example:
|
||||
* {
|
||||
* pathname: 'app/kibana',
|
||||
* hash: '/discover'
|
||||
* }
|
||||
* @return {string}
|
||||
*/
|
||||
module.exports = function getPage(config, pathname) {
|
||||
return url.format(_.assign(config, {
|
||||
pathname: pathname
|
||||
}));
|
||||
module.exports = function getPage(config, app) {
|
||||
return url.format(_.assign(config, app));
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue