diff --git a/LICENSE.md b/LICENSE.md index 484bbafe7b7a..d049908847b8 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright 2012–2014 Elasticsearch BV +Copyright 2012–2015 Elasticsearch BV Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/package.json b/package.json index 0296d50c30d7..067eaea06baa 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "good-squeeze": "2.1.0", "gridster": "0.5.6", "hapi": "8.8.1", + "httpolyglot": "0.1.1", "imports-loader": "0.6.4", "jade": "1.11.0", "jade-loader": "0.7.1", diff --git a/src/cli/cluster/cluster_manager.js b/src/cli/cluster/cluster_manager.js index dbc12f66ccf2..033dcf07dce0 100644 --- a/src/cli/cluster/cluster_manager.js +++ b/src/cli/cluster/cluster_manager.js @@ -15,28 +15,38 @@ module.exports = class ClusterManager { this.log = new Log(opts.quiet, opts.silent); this.addedCount = 0; - this.basePathProxy = new BasePathProxy(this, settings); + const serverArgv = []; + const optimizerArgv = [ + '--plugins.initialize=false', + '--server.autoListen=false', + ]; + + if (opts.basePath) { + this.basePathProxy = new BasePathProxy(this, settings); + + optimizerArgv.push( + `--server.basePath=${this.basePathProxy.basePath}` + ); + + serverArgv.push( + `--server.port=${this.basePathProxy.targetPort}`, + `--server.basePath=${this.basePathProxy.basePath}` + ); + } this.workers = [ this.optimizer = new Worker({ type: 'optmzr', title: 'optimizer', log: this.log, - argv: compact([ - '--plugins.initialize=false', - '--server.autoListen=false', - `--server.basePath=${this.basePathProxy.basePath}` - ]), + argv: optimizerArgv, watch: false }), this.server = new Worker({ type: 'server', log: this.log, - argv: compact([ - `--server.port=${this.basePathProxy.targetPort}`, - `--server.basePath=${this.basePathProxy.basePath}` - ]) + argv: serverArgv }) ]; @@ -60,7 +70,9 @@ module.exports = class ClusterManager { startCluster() { this.setupManualRestart(); invoke(this.workers, 'start'); - this.basePathProxy.listen(); + if (this.basePathProxy) { + this.basePathProxy.listen(); + } } setupWatching() { diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 49df7e77f8d2..0534021e7e94 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -107,6 +107,7 @@ module.exports = function (program) { command .option('--dev', 'Run the server with development mode defaults') .option('--no-ssl', 'Don\'t run the dev server using HTTPS') + .option('--no-base-path', 'Don\'t put a proxy in front of the dev server, which adds a random basePath') .option('--no-watch', 'Prevents automatic restarts of the server in --dev mode'); } diff --git a/src/plugins/kibana/public/dashboard/index.html b/src/plugins/kibana/public/dashboard/index.html index d2a2688c801c..9a3f94fa3832 100644 --- a/src/plugins/kibana/public/dashboard/index.html +++ b/src/plugins/kibana/public/dashboard/index.html @@ -1,5 +1,5 @@
- +
- +
- +
+ + `; + + +describe('navbar directive', function () { + let $rootScope; + let $compile; + let stubRegistry; + + beforeEach(function () { + ngMock.module('kibana', function (PrivateProvider) { + stubRegistry = new Registry({ + index: ['name'], + group: ['appName'], + order: ['order'] + }); + + PrivateProvider.swap(navbarExtensionsRegistry, stubRegistry); + }); + + ngMock.module('kibana/navbar'); + + // Create the scope + ngMock.inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $compile = $injector.get('$compile'); + }); + }); + + function init(markup = defaultMarkup) { + // Give us a scope + const $el = angular.element(markup); + $compile($el)($rootScope); + $el.scope().$digest(); + return $el; + } + + describe('incorrect use', function () { + it('should throw if missing a name property', function () { + const markup = ``; + expect(() => init(markup)).to.throwException(/requires a name attribute/); + }); + + it('should throw if missing a button group', function () { + const markup = ``; + expect(() => init(markup)).to.throwException(/must have exactly 1 button group/); + }); + + it('should throw if multiple button groups', function () { + const markup = ` + + + `; + expect(() => init(markup)).to.throwException(/must have exactly 1 button group/); + }); + + it('should throw if button group not direct child', function () { + const markup = `
`; + expect(() => init(markup)).to.throwException(/must have exactly 1 button group/); + }); + }); + + describe('injecting extensions', function () { + function registerExtension(def = {}) { + stubRegistry.register(function () { + return _.defaults(def, { + name: 'exampleButton', + appName: 'testing', + order: 0, + template: ` + ` + }); + }); + } + + it('should use the default markup', function () { + var $el = init(); + expect($el.find('.button-group button').length).to.equal(3); + }); + + it('should append to end then order == 0', function () { + registerExtension({ order: 0 }); + var $el = init(); + + expect($el.find('.button-group button').length).to.equal(4); + expect($el.find('.button-group button').last().hasClass('test-button')).to.be.ok(); + }); + + it('should append to end then order > 0', function () { + registerExtension({ order: 1 }); + var $el = init(); + + expect($el.find('.button-group button').length).to.equal(4); + expect($el.find('.button-group button').last().hasClass('test-button')).to.be.ok(); + }); + + it('should append to end then order < 0', function () { + registerExtension({ order: -1 }); + var $el = init(); + + expect($el.find('.button-group button').length).to.equal(4); + expect($el.find('.button-group button').first().hasClass('test-button')).to.be.ok(); + }); + }); +}); diff --git a/src/ui/public/navbar/navbar.js b/src/ui/public/navbar/navbar.js new file mode 100644 index 000000000000..2e49a760708b --- /dev/null +++ b/src/ui/public/navbar/navbar.js @@ -0,0 +1,56 @@ +const _ = require('lodash'); +const $ = require('jquery'); +const navbar = require('ui/modules').get('kibana/navbar'); + +require('ui/render_directive'); + +navbar.directive('navbar', function (Private, $compile) { + const navbarExtensions = Private(require('ui/registry/navbar_extensions')); + const getExtensions = _.memoize(function (name) { + if (!name) throw new Error('navbar directive requires a name attribute'); + return _.sortBy(navbarExtensions.byAppName[name], 'order'); + }); + + return { + restrict: 'E', + template: function ($el, $attrs) { + const $buttonGroup = $el.children('.button-group'); + if ($buttonGroup.length !== 1) throw new Error('navbar must have exactly 1 button group'); + + const extensions = getExtensions($attrs.name); + const buttons = $buttonGroup.children().detach().toArray(); + const controls = [ + ...buttons.map(function (button) { + return { + order: 0, + $el: $(button), + }; + }), + ...extensions.map(function (extension, i) { + return { + order: extension.order, + index: i, + extension: extension, + }; + }), + ]; + + _.sortBy(controls, 'order').forEach(function (control) { + if (control.$el) { + return $buttonGroup.append(control.$el); + } + + const { extension, index } = control; + const $ext = $(``); + $ext.html(extension.template); + $buttonGroup.append($ext); + }); + + return $el.html(); + }, + controllerAs: 'navbar', + controller: function ($attrs) { + this.extensions = getExtensions($attrs.name); + } + }; +}); diff --git a/src/ui/public/styles/base.less b/src/ui/public/styles/base.less index 016c09af33c3..6dcb58bf4b15 100644 --- a/src/ui/public/styles/base.less +++ b/src/ui/public/styles/base.less @@ -457,3 +457,14 @@ fieldset { overflow-y: hidden; } } + +.list-group { + .list-group-item { + &.active, + &.active:hover, + &.active:focus { + background-color: @list-group-menu-item-active-bg; + cursor: default; + } + } +} diff --git a/tasks/config/run.js b/tasks/config/run.js index e5c6fbb7fcc0..aacd6a045378 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -86,6 +86,8 @@ module.exports = function (grunt) { args: [ '--dev', '--no-watch', + '--no-ssl', + '--no-base-path', '--server.port=5610', '--optimize.lazyPort=5611', '--optimize.lazyPrebuild=true', diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 100336e8fb80..ffd39f1286c1 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -48,10 +48,11 @@ define(function (require) { bdd.describe('query', function () { var queryName1 = 'Query # 1'; + var fromTimeString = 'September 19th 2015, 06:31:44.000'; + var toTimeString = 'September 23rd 2015, 18:31:44.000'; - bdd.it('should show correct time range string', function () { - var expectedTimeRangeString = - 'September 19th 2015, 06:31:44.000 to September 23rd 2015, 18:31:44.000'; + bdd.it('should show correct time range string by timepicker', function () { + var expectedTimeRangeString = fromTimeString + ' to ' + toTimeString; return discoverPage.getTimespanText() .then(function (actualTimeString) { expect(actualTimeString).to.be(expectedTimeRangeString); @@ -59,6 +60,7 @@ define(function (require) { .catch(common.handleError(this)); }); + bdd.it('save query should show toast message and display query name', function () { var expectedSavedQueryMessage = 'Discover: Saved Data Source "' + queryName1 + '"'; return discoverPage.saveSearch(queryName1) @@ -91,6 +93,19 @@ define(function (require) { .catch(common.handleError(this)); }); + bdd.it('should show the correct hit count', function () { + var expectedHitCount = '14,004'; + return common.tryForTime(20 * 1000, function tryingForTime() { + return discoverPage.getHitCount() + .then(function compareData(hitCount) { + expect(hitCount).to.be(expectedHitCount); + }); + }) + .catch(common.handleError(this)); + }); + + + bdd.it('should show the correct bar chart', function () { var expectedBarChartData = [ '0', '0', '0', '0', '0', '0', '2.7056249999999977', '14.771249999999995', '54.112500000000004', @@ -104,25 +119,176 @@ define(function (require) { ]; return common.sleep(4000) .then(function () { - return common.tryForTime(20 * 1000, function tryingForTime() { - return discoverPage.getBarChartData() - .then(function compareData(paths) { - // the largest bars are over 100 pixels high so this is less than 1% tolerance - var barHeightTolerance = 1; - for (var y = 0; y < expectedBarChartData.length; y++) { - common.debug(y + ': expected = ' + expectedBarChartData[y] + ', actual = ' + paths[y] + - ', Pass = ' + (Math.abs(expectedBarChartData[y] - paths[y]) < barHeightTolerance)); - }; - for (var x = 0; x < expectedBarChartData.length; x++) { - expect(Math.abs(expectedBarChartData[x] - paths[x]) < barHeightTolerance).to.be.ok(); - } - }); - }); + return verifyChartData(expectedBarChartData); + }) + .catch(common.handleError(this)); + }); + + bdd.it('should show correct time range string in chart', function () { + var expectedTimeRangeString = fromTimeString + ' - ' + toTimeString; + return discoverPage.getChartTimespan() + .then(function (actualTimeString) { + expect(actualTimeString).to.be(expectedTimeRangeString); + }) + .catch(common.handleError(this)); + }); + + bdd.it('should show correct initial chart interval of 3 hours', function () { + var expectedChartInterval = 'by 3 hours'; + return discoverPage.getChartInterval() + .then(function (actualInterval) { + expect(actualInterval).to.be(expectedChartInterval); + }) + .catch(common.handleError(this)); + }); + + bdd.it('should show correct data for chart interval Hourly', function () { + var chartInterval = 'Hourly'; + var expectedBarChartData = [ '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '1.2763636363636266', '1.914545454545447', + '4.680000000000007', '6.594545454545454', '11.061818181818168', '25.314545454545453', + '38.50363636363636', '46.374545454545455', '72.53999999999999', '93.60000000000001', + '102.10909090909091', '109.97999999999999', '111.04363636363637', '94.87636363636364', + '85.72909090909091', '68.28545454545454', '54.88363636363636', '36.58909090909091', + '20.209090909090904', '11.700000000000003', '8.083636363636359', '5.105454545454549', + '0.6381818181818204', '0.8509090909090986', '2.3400000000000034', '2.978181818181824', + '3.61636363636363', '8.083636363636359', '10.423636363636362', '24.46363636363637', + '32.33454545454545', '45.94909090909091', '67.00909090909092', '85.51636363636365', + '94.87636363636364', '109.1290909090909', '110.61818181818181', '100.83272727272727', + '89.55818181818182', '65.30727272727273', '48.92727272727274', '36.16363636363636', + '21.059999999999988', '10.210909090909098', '6.38181818181819', '3.190909090909088', + '2.127272727272725', '0.4254545454545422', '1.701818181818183', '1.4890909090909048', + '3.61636363636363', '7.232727272727274', '7.870909090909095', '22.123636363636365', + '32.54727272727273', '51.267272727272726', '66.58363636363637', '85.94181818181818', + '104.66181818181818', '108.91636363636364', '107.00181818181818', '100.62', + '80.62363636363636', '62.32909090909091', '58.92545454545455', '33.18545454545455', + '21.059999999999988', '11.274545454545446', '10.423636363636362', '3.403636363636366', + '1.914545454545447', '0.8509090909090986', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0' + ]; + return discoverPage.setChartInterval(chartInterval) + .then(function () { + return common.sleep(8000); + }) + .then(function () { + return verifyChartData(expectedBarChartData); + }) + .catch(common.handleError(this)); + }); + + bdd.it('should show correct data for chart interval Daily', function () { + var chartInterval = 'Daily'; + var expectedBarChartData = [ '0', '111.3138', '107.96759999999999', '108.4122', '0']; + return discoverPage.setChartInterval(chartInterval) + .then(function () { + return common.sleep(8000); + }) + .then(function () { + return verifyChartData(expectedBarChartData); + }) + .catch(common.handleError(this)); + }); + + bdd.it('should show correct data for chart interval Weekly', function () { + var chartInterval = 'Weekly'; + var expectedBarChartData = [ '55.6569', '108.1899']; + return discoverPage.setChartInterval(chartInterval) + .then(function () { + return common.sleep(2000); + }) + .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 = [ '102.404']; + return discoverPage.setChartInterval(chartInterval) + .then(function () { + return common.sleep(2000); + }) + .then(function () { + return verifyChartData(expectedBarChartData); + }) + .catch(common.handleError(this)); + }); + + bdd.it('should show correct data for chart interval Yearly', function () { + var chartInterval = 'Yearly'; + var expectedBarChartData = [ '102.404']; + return discoverPage.setChartInterval(chartInterval) + .then(function () { + return common.sleep(2000); + }) + .then(function () { + return verifyChartData(expectedBarChartData); }) .catch(common.handleError(this)); }); + + bdd.it('should show correct data for chart interval Auto', function () { + var chartInterval = 'Auto'; + var expectedBarChartData = [ '0', '0', '0', '0', '0', '0', + '2.7056249999999977', '14.771249999999995', '54.112500000000004', + '105.080625', '100.25437500000001', '54.916875', '13.747499999999988', + '2.266874999999999', '3.0712500000000063', '14.771249999999995', + '49.944374999999994', '99.523125', '103.471875', '51.699375', + '12.943124999999995', '1.9743749999999949', '2.3400000000000034', + '12.796875', '51.699375', '102.96000000000001', '99.08437500000001', + '53.08875', '14.698125000000005', '2.1206249999999898', '0', '0', + '0', '0', '0', '0', '0' + ]; + return discoverPage.setChartInterval(chartInterval) + .then(function () { + return common.sleep(4000); + }) + .then(function () { + return verifyChartData(expectedBarChartData); + }) + .catch(common.handleError(this)); + }); + + bdd.it('should show Auto chart interval of 3 hours', function () { + var expectedChartInterval = 'by 3 hours'; + return discoverPage.getChartInterval() + .then(function (actualInterval) { + expect(actualInterval).to.be(expectedChartInterval); + }) + .catch(common.handleError(this)); + }); + + + function verifyChartData(expectedBarChartData) { + return common.tryForTime(20 * 1000, function tryingForTime() { + return discoverPage.getBarChartData() + .then(function compareData(paths) { + // the largest bars are over 100 pixels high so this is less than 1% tolerance + var barHeightTolerance = 1; + var stringResults = ''; + var hasFailure = false; + for (var y = 0; y < expectedBarChartData.length; y++) { + stringResults += y + ': expected = ' + expectedBarChartData[y] + ', actual = ' + paths[y] + + ', Pass = ' + (Math.abs(expectedBarChartData[y] - paths[y]) < barHeightTolerance); + if ((Math.abs(expectedBarChartData[y] - paths[y]) > barHeightTolerance)) { + hasFailure = true; + }; + }; + if (hasFailure) { + common.log(stringResults); + common.log(paths); + } + for (var x = 0; x < expectedBarChartData.length; x++) { + expect(Math.abs(expectedBarChartData[x] - paths[x]) < barHeightTolerance).to.be.ok(); + } + }); + }); + + } + }); }); }; diff --git a/test/functional/apps/discover/_field_data.js b/test/functional/apps/discover/_field_data.js new file mode 100644 index 000000000000..e50f65678ca2 --- /dev/null +++ b/test/functional/apps/discover/_field_data.js @@ -0,0 +1,268 @@ +define(function (require) { + var Common = require('../../../support/pages/Common'); + var HeaderPage = require('../../../support/pages/HeaderPage'); + var SettingsPage = require('../../../support/pages/settings_page'); + var DiscoverPage = require('../../../support/pages/DiscoverPage'); + var expect = require('intern/dojo/node!expect.js'); + + return function (bdd, scenarioManager) { + bdd.describe('discover app', function describeIndexTests() { + var common; + var headerPage; + var settingsPage; + var discoverPage; + + bdd.before(function () { + common = new Common(this.remote); + headerPage = new HeaderPage(this.remote); + settingsPage = new SettingsPage(this.remote); + discoverPage = new DiscoverPage(this.remote); + var fromTime = '2015-09-19 06:31:44.000'; + var toTime = '2015-09-23 18:31:44.000'; + + // start each test with an empty kibana index + return scenarioManager.reload('emptyKibana') + // and load a set of makelogs data + .then(function loadIfEmptyMakelogs() { + return scenarioManager.loadIfEmpty('logstashFunctional'); + }) + .then(function (navigateTo) { + common.debug('navigateTo'); + return settingsPage.navigateTo(); + }) + .then(function () { + common.debug('createIndexPattern'); + return settingsPage.createIndexPattern(); + }) + .then(function () { + common.debug('discover'); + return common.navigateToApp('discover'); + }) + .then(function () { + common.debug('setAbsoluteRange'); + return headerPage.setAbsoluteRange(fromTime, toTime); + }) + .catch(common.handleError(this)); + }); + + + bdd.describe('field data', function () { + var queryName1 = 'Query # 1'; + var fromTimeString = 'September 19th 2015, 06:31:44.000'; + var toTimeString = 'September 23rd 2015, 18:31:44.000'; + + + bdd.it('search php should show the correct hit count', function () { + var expectedHitCount = '445'; + return discoverPage.query('php') + .then(function () { + return common.tryForTime(20 * 1000, function tryingForTime() { + return discoverPage.getHitCount() + .then(function compareData(hitCount) { + expect(hitCount).to.be(expectedHitCount); + }); + }); + }) + .catch(common.handleError(this)); + }); + + bdd.it('the search term should be highlighted in the field data', function () { + // marks is the style that highlights the text in yellow + return discoverPage.getMarks() + .then(function (marks) { + expect(marks.length).to.be(50); + expect(marks.indexOf('php')).to.be(0); + }) + .catch(common.handleError(this)); + }); + + + bdd.it('search _type:apache should show the correct hit count', function () { + var expectedHitCount = '11,156'; + return discoverPage.query('_type:apache') + .then(function () { + return common.tryForTime(20 * 1000, function tryingForTime() { + return discoverPage.getHitCount() + .then(function compareData(hitCount) { + expect(hitCount).to.be(expectedHitCount); + }); + }); + }) + .catch(common.handleError(this)); + }); + + bdd.it('doc view should show Time and _source columns', function () { + var expectedHeader = 'Time _source'; + return discoverPage.getDocHeader() + .then(function (header) { + expect(header).to.be(expectedHeader); + }) + .catch(common.handleError(this)); + }); + + bdd.it('doc view should show oldest time first', function () { + // Note: Could just check the timestamp, but might as well check that the whole doc is as expected. + var ExpectedDoc = + 'September 22nd 2015, 23:50:13.253 index:logstash-2015.09.22 @timestamp:September 22nd 2015, 23:50:13.253' + + ' ip:238.171.34.42 extension:jpg response:200 geo.coordinates:{ "lat": 38.66494528, "lon": -88.45299556' + + ' } geo.src:FR geo.dest:KH geo.srcdest:FR:KH @tags:success, info utc_time:September 22nd 2015,' + + ' 23:50:13.253 referer:http://twitter.com/success/nancy-currie agent:Mozilla/4.0 (compatible; MSIE 6.0;' + + ' Windows NT 5.1; SV1; .NET CLR 1.1.4322) clientip:238.171.34.42 bytes:7,124' + + ' host:media-for-the-masses.theacademyofperformingartsandscience.org request:/uploads/karl-henize.jpg' + + ' url:https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/karl-henize.jpg' + + ' @message:238.171.34.42 - - [2015-09-22T23:50:13.253Z] "GET /uploads/karl-henize.jpg HTTP/1.1" 200 7124' + + ' "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)" spaces:this is a' + + ' thing with lots of spaces wwwwoooooo xss:' + + ' headings:

alexander-viktorenko

, http://nytimes.com/warning/michael-massimino' + + ' links:@www.slate.com, http://www.slate.com/security/frederick-w-leslie, www.www.slate.com' + + ' relatedContent:{ "url": "http://www.laweekly.com/music/bjork-at-the-nokia-theatre-12-12-2408191",' + + ' "og:type": "article", "og:title": "Bjork at the Nokia Theatre, 12/12", "og:description": "Bjork at the' + + ' Nokia Theater, December 12 By Randall Roberts Last night’s Bjork show at the Dystopia –' + + ' er, I mean Nokia -- Theatre downtown di...", "og:url": "' + + 'http://www.laweekly.com/music/bjork-at-the-nokia-theatre-12-12-2408191", "article:published_time":' + + ' "2007-12-13T12:19:35-08:00", "article:modified_time": "2014-11-27T08:28:42-08:00", "article:section":' + + ' "Music", "og:image": "' + + 'http://IMAGES1.laweekly.com/imager/bjork-at-the-nokia-theatre-12-12/u/original/2470701/bjorktn003.jpg",' + + ' "og:image:height": "334", "og:image:width": "480", "og:site_name": "LA Weekly", "twitter:title":' + + ' "Bjork at the Nokia Theatre, 12/12", "twitter:description": "Bjork at the Nokia Theater, December 12' + + ' By Randall Roberts Last night’s Bjork show at the Dystopia – er, I mean Nokia -- Theatre' + + ' downtown di...", "twitter:card": "summary", "twitter:image": "' + + 'http://IMAGES1.laweekly.com/imager/bjork-at-the-nokia-theatre-12-12/u/original/2470701/bjorktn003.jpg",' + + ' "twitter:site": "@laweekly" }, { "url": "' + + 'http://www.laweekly.com/music/the-rapture-at-the-mayan-7-25-2401011", "og:type": "article", "og:title":' + + ' "The Rapture at the Mayan, 7/25", "og:description": "If you haven’t yet experienced the' + + ' phenomenon of people walk-dancing, apparently the best place to witness this is at a Rapture show.' + + ' Here’s...", "og:url": "http://www.laweekly.com/music/the-rapture-at-the-mayan-7-25-2401011",' + + ' "article:published_time": "2007-07-26T12:42:30-07:00", "article:modified_time":' + + ' "2014-11-27T08:00:51-08:00", "article:section": "Music", "og:image": "' + + 'http://IMAGES1.laweekly.com/imager/the-rapture-at-the-mayan-7-25/u/original/2463272/rapturetn05.jpg",' + + ' "og:image:height": "321", "og:image:width": "480", "og:site_name": "LA Weekly", "twitter:title": "The' + + ' Rapture at the Mayan, 7/25", "twitter:description": "If you haven’t yet experienced the' + + ' phenomenon of people walk-dancing, apparently the best place to witness this is at a Rapture show.' + + ' Here’s...", "twitter:card": "summary", "twitter:image": "' + + 'http://IMAGES1.laweekly.com/imager/the-rapture-at-the-mayan-7-25/u/original/2463272/rapturetn05.jpg",' + + ' "twitter:site": "@laweekly" } machine.os:win 7 machine.ram:7,516,192,768 _id:AU_x3_g4GFA8no6QjkYX' + + ' _type:apache _index:logstash-2015.09.22 _score: relatedContent.article:modified_time:November 27th' + + ' 2014, 16:00:51.000, November 27th 2014, 16:28:42.000 relatedContent.article:published_time:July 26th' + + ' 2007, 19:42:30.000, December 13th 2007, 20:19:35.000'; + return discoverPage.getDocTableIndex(1) + .then(function (rowData) { + expect(rowData).to.be(ExpectedDoc); + }) + .catch(common.handleError(this)); + }); + + bdd.it('doc view should sort ascending', function () { + // Note: Could just check the timestamp, but might as well check that the whole doc is as expected. + var ExpectedDoc = + 'September 20th 2015, 00:00:00.000 index:logstash-2015.09.20 @timestamp:September 20th 2015, 00:00:00.000' + + ' ip:143.84.142.7 extension:jpg response:200 geo.coordinates:{ "lat": 38.68407028, "lon": -120.9871642 }' + + ' geo.src:ES geo.dest:US geo.srcdest:ES:US @tags:error, info utc_time:September 20th 2015, 00:00:00.000' + + ' referer:http://www.slate.com/success/vladimir-kovalyonok agent:Mozilla/4.0 (compatible; MSIE 6.0;' + + ' Windows NT 5.1; SV1; .NET CLR 1.1.4322) clientip:143.84.142.7 bytes:1,623' + + ' host:media-for-the-masses.theacademyofperformingartsandscience.org request:/uploads/steven-hawley.jpg' + + ' url:https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/steven-hawley.jpg' + + ' @message:143.84.142.7 - - [2015-09-20T00:00:00.000Z] "GET /uploads/steven-hawley.jpg HTTP/1.1" 200' + + ' 1623 "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)" spaces:this is a' + + ' thing with lots of spaces wwwwoooooo xss:' + + ' headings:

kimiya-yui

, http://www.slate.com/success/koichi-wakata' + + ' links:thomas-marshburn@twitter.com, http://www.slate.com/info/michael-p-anderson, www.twitter.com' + + ' relatedContent:{ "url":' + + ' "http://www.laweekly.com/music/jay-electronica-much-better-than-his-name-would-suggest-2412364",' + + ' "og:type": "article", "og:title": "Jay Electronica: Much Better Than His Name Would Suggest",' + + ' "og:description": "You may not know who Jay Electronica is yet, but I'm willing to bet that you' + + ' would had he chosen a better name. Jay Electronica does not sound like the ...", "og:url":' + + ' "http://www.laweekly.com/music/jay-electronica-much-better-than-his-name-would-suggest-2412364",' + + ' "article:published_time": "2008-04-04T16:00:00-07:00", "article:modified_time":' + + ' "2014-11-27T08:01:03-08:00", "article:section": "Music", "og:site_name": "LA Weekly", "twitter:title":' + + ' "Jay Electronica: Much Better Than His Name Would Suggest", "twitter:description": "You may not know' + + ' who Jay Electronica is yet, but I'm willing to bet that you would had he chosen a better name.' + + ' Jay Electronica does not sound like the ...", "twitter:card": "summary", "twitter:site": "@laweekly"' + + ' }, { "url": "http://www.laweekly.com/news/mandoe-on-gower-near-fountain-2368123", "og:type":' + + ' "article", "og:title": "MANDOE On Gower Near Fountain", "og:description": "MANDOE has a stunner on a' + + ' wall north of an east-west street crossing Gower around Fountain (but not on Fountain). MADNOE, PROSE' + + ' and FUKM are listed on t...", "og:url": "' + + 'http://www.laweekly.com/news/mandoe-on-gower-near-fountain-2368123", "article:published_time":' + + ' "2008-04-25T07:26:41-07:00", "article:modified_time": "2014-10-28T15:00:08-07:00", "article:section":' + + ' "News", "og:image": "' + + 'http://images1.laweekly.com/imager/mandoe-on-gower-near-fountain/u/original/2430891/img_6648.jpg",' + + ' "og:image:height": "640", "og:image:width": "480", "og:site_name": "LA Weekly", "twitter:title": ' + + '"MANDOE On Gower Near Fountain", "twitter:description": "MANDOE has a stunner on a wall north of an' + + ' east-west street crossing Gower around Fountain (but not on Fountain). MADNOE, PROSE and FUKM are' + + ' listed on t...", "twitter:card": "summary", "twitter:image": "' + + 'http://images1.laweekly.com/imager/mandoe-on-gower-near-fountain/u/original/2430891/img_6648.jpg", ' + + '"twitter:site": "@laweekly" }, { "url": "http://www.laweekly.com/arts/meghan-finds-the-love-2373346",' + + ' "og:type": "article", "og:title": "Meghan Finds The Love", "og:description": "LA Weekly is the' + + ' definitive source of information for news, music, movies, restaurants, reviews, and events in Los' + + ' Angeles.", "og:url": "http://www.laweekly.com/arts/meghan-finds-the-love-2373346",' + + ' "article:published_time": "2005-10-20T18:10:25-07:00", "article:modified_time":' + + ' "2014-11-25T19:52:35-08:00", "article:section": "Arts", "og:site_name": "LA Weekly", "twitter:title":' + + ' "Meghan Finds The Love", "twitter:description": "LA Weekly is the definitive source of information for' + + ' news, music, movies, restaurants, reviews, and events in Los Angeles.", "twitter:card": "summary",' + + ' "twitter:site": "@laweekly" }, { "url": "http://www.laweekly.com/arts/these-clowns-are-with-me-2371051' + + '", "og:type": "article", "og:title": "These Clowns Are With Me", "og:description": "    I' + + ' didn't mean to blow off all my responsibilities yesterday, but when a schmoozy Hollywood luncheon' + + ' turns into a full-on party by 3pm, and...", "og:url": "' + + 'http://www.laweekly.com/arts/these-clowns-are-with-me-2371051", "article:published_time": ' + + '"2006-03-04T17:03:42-08:00", "article:modified_time": "2014-11-25T17:05:47-08:00", "article:section":' + + ' "Arts", "og:image": "' + + 'http://images1.laweekly.com/imager/these-clowns-are-with-me/u/original/2434556/e4b8scd.jpg",' + + ' "og:image:height": "375", "og:image:width": "500", "og:site_name": "LA Weekly", "twitter:title":' + + ' "These Clowns Are With Me", "twitter:description": "    I didn't mean to blow off all' + + ' my responsibilities yesterday, but when a schmoozy Hollywood luncheon turns into a full-on party by' + + ' 3pm, and...", "twitter:card": "summary", "twitter:image": "' + + 'http://images1.laweekly.com/imager/these-clowns-are-with-me/u/original/2434556/e4b8scd.jpg",' + + ' "twitter:site": "@laweekly" }, { "url": "http://www.laweekly.com/arts/shopping-daze-2373807",' + + ' "og:type": "article", "og:title": "Shopping Daze", "og:description": "LA Weekly is the definitive ' + + 'source of information for news, music, movies, restaurants, reviews, and events in Los Angeles.",' + + ' "og:url": "http://www.laweekly.com/arts/shopping-daze-2373807", "article:published_time":' + + ' "2006-12-13T12:12:04-08:00", "article:modified_time": "2014-11-25T20:15:21-08:00", "article:section":' + + ' "Arts", "og:site_name": "LA Weekly", "twitter:title": "Shopping Daze", "twitter:description": "LA' + + ' Weekly is the definitive source of information for news, music, movies, restaurants, reviews, and' + + ' events in Los Angeles.", "twitter:card": "summary", "twitter:site": "@laweekly" } machine.os:osx' + + ' machine.ram:15,032,385,536 _id:AU_x3_g3GFA8no6QjkFm _type:apache _index:logstash-2015.09.20 _score:' + + ' relatedContent.article:modified_time:October 28th 2014, 22:00:08.000, November 26th 2014,' + + ' 01:05:47.000, November 26th 2014, 03:52:35.000, November 26th 2014, 04:15:21.000, November 27th 2014,' + + ' 16:01:03.000 relatedContent.article:published_time:October 21st 2005, 01:10:25.000, March 5th 2006,' + + ' 01:03:42.000, December 13th 2006, 20:12:04.000, April 4th 2008, 23:00:00.000, April 25th 2008,' + + ' 14:26:41.000'; + return discoverPage.clickDocSortDown() + .then(function () { + // we don't technically need this sleep here because the tryForTime will retry and the + // results will match on the 2nd or 3rd attempt, but that debug output is huge in this + // case and it can be avoided with just a few seconds sleep. + return common.sleep(2000); + }) + .then(function () { + return common.tryForTime(20 * 1000, function tryingForTime() { + return discoverPage.getDocTableIndex(1) + .then(function (rowData) { + expect(rowData).to.be(ExpectedDoc); + }); + }); + }) + .catch(common.handleError(this)); + }); + + + bdd.it('a bad syntax query should show an error message', function () { + var expectedHitCount = '1011,156'; + var expectedError = 'Discover: Failed to parse query [xxx(yyy]'; + return discoverPage.query('xxx(yyy') + .then(function () { + return headerPage.getToastMessage(); + }) + .then(function (toastMessage) { + expect(toastMessage).to.be(expectedError); + }) + .then(function () { + return headerPage.clickToastOK(); + }) + .catch(common.handleError(this)); + }); + + + }); + }); + }; +}); diff --git a/test/functional/apps/discover/index.js b/test/functional/apps/discover/index.js index c2677dd248a3..eef3af445e9a 100644 --- a/test/functional/apps/discover/index.js +++ b/test/functional/apps/discover/index.js @@ -4,6 +4,7 @@ define(function (require) { var url = require('intern/dojo/node!url'); var ScenarioManager = require('intern/dojo/node!../../../fixtures/scenarioManager'); var discoverTest = require('./_discover'); + var fieldData = require('./_field_data'); bdd.describe('discover app', function () { var scenarioManager; @@ -22,5 +23,7 @@ define(function (require) { discoverTest(bdd, scenarioManager); + fieldData(bdd, scenarioManager); + }); }); diff --git a/test/serverConfig.js b/test/serverConfig.js index e88daa58e978..ea3438e728bb 100644 --- a/test/serverConfig.js +++ b/test/serverConfig.js @@ -1,3 +1,5 @@ +var shield = require('./shield'); + var kibanaURL = '/app/kibana'; module.exports = { @@ -11,13 +13,13 @@ module.exports = { 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, - auth: 'user:notsecure' + auth: shield.kibanaUser.username + ':' + shield.kibanaUser.password }, 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, - auth: 'admin:notsecure' + auth: shield.admin.username + ':' + shield.admin.password } }, apps: { diff --git a/test/shield.js b/test/shield.js new file mode 100644 index 000000000000..826945be5a22 --- /dev/null +++ b/test/shield.js @@ -0,0 +1,16 @@ +const env = process.env; + +exports.kibanaUser = { + username: env.SHIELD_KIBANA_USER || 'user', + password: env.SHIELD_KIBANA_USER_PASS || 'notsecure' +}; + +exports.kibanaServer = { + username: env.SHIELD_KIBANA_SERVER || 'kibana', + password: env.SHIELD_KIBANA_SERVER_PASS || 'notsecure' +}; + +exports.admin = { + username: env.SHIELD_ADMIN || 'admin', + password: env.SHIELD_ADMIN_PASS || 'notsecure' +}; diff --git a/test/support/pages/DiscoverPage.js b/test/support/pages/DiscoverPage.js index eb611669fe75..cbaa0073f43a 100644 --- a/test/support/pages/DiscoverPage.js +++ b/test/support/pages/DiscoverPage.js @@ -32,6 +32,12 @@ define(function (require) { .getVisibleText(); }, + getChartTimespan: function getChartTimespan() { + return thisTime + .findByCssSelector('center.small > span:nth-child(1)') + .getVisibleText(); + }, + saveSearch: function saveSearch(searchName) { var self = this; return self.clickSaveSearchButton() @@ -85,7 +91,6 @@ define(function (require) { }, getBarChartData: function getBarChartData() { - common.debug('in getBarChartData'); return thisTime .findAllByCssSelector('rect[data-label="Count"]') .then(function (chartData) { @@ -101,6 +106,75 @@ define(function (require) { .then(function (bars) { return bars; }); + }, + + getChartInterval: function getChartInterval() { + return thisTime + .findByCssSelector('span.results-interval:nth-child(2) > a:nth-child(1)') + .getVisibleText(); + }, + + setChartInterval: function setChartInterval(interval) { + return thisTime + .findByCssSelector('span.results-interval:nth-child(2) > a:nth-child(1)') + .click() + .catch(function () { + // in some cases we have the link above, but after we've made a + // selection we just have a select list. + }) + .then(function () { + return thisTime + .findByCssSelector('option[label="' + interval + '"]') + .click(); + }); + }, + + getHitCount: function getHitCount() { + return thisTime + .findByCssSelector('strong.discover-info-hits') + .getVisibleText(); + }, + + query: function query(queryString) { + return thisTime + .findByCssSelector('input[aria-label="Search input"]') + .clearValue() + .type(queryString) + .then(function () { + return thisTime + .findByCssSelector('button[aria-label="Search"]') + .click(); + }); + }, + + getDocHeader: function getDocHeader() { + return thisTime + .findByCssSelector('thead.ng-isolate-scope > tr:nth-child(1)') + .getVisibleText(); + }, + + getDocTableIndex: function getDocTableIndex(index) { + return thisTime + .findByCssSelector('tr.discover-table-row:nth-child(' + (index) + ')') + .getVisibleText(); + }, + + clickDocSortDown: function clickDocSortDown() { + return thisTime + .findByCssSelector('.fa-sort-down') + .click(); + }, + + clickDocSortUp: function clickDocSortUp() { + return thisTime + .findByCssSelector('.fa-sort-up') + .click(); + }, + + getMarks: function getMarks() { + return thisTime + .findAllByCssSelector('mark') + .getVisibleText(); } }; diff --git a/test/support/pages/HeaderPage.js b/test/support/pages/HeaderPage.js index c1e8767baf56..5a78ebb7736e 100644 --- a/test/support/pages/HeaderPage.js +++ b/test/support/pages/HeaderPage.js @@ -137,6 +137,13 @@ define(function (require) { }); }, + clickToastOK: function clickToastOK() { + return this.remote + .setFindTimeout(defaultTimeout) + .findByCssSelector('button[ng-if="notif.accept"]') + .click(); + }, + getSpinnerDone: function getSpinnerDone() { var self = this; return this.remote diff --git a/test/utils/kbn_server.js b/test/utils/kbn_server.js index d97d586e7a79..35a0bc3aa1c5 100644 --- a/test/utils/kbn_server.js +++ b/test/utils/kbn_server.js @@ -1,6 +1,7 @@ import { defaultsDeep, set } from 'lodash'; import requirefrom from 'requirefrom'; import { header as basicAuthHeader } from './base_auth'; +import { kibanaUser, kibanaServer } from '../shield'; const src = requirefrom('src'); const KbnServer = src('server/KbnServer'); @@ -26,8 +27,8 @@ const SERVER_DEFAULTS = { }, elasticsearch: { url: 'http://localhost:9210', - username: 'kibana', - password: 'notsecure' + username: kibanaServer.username, + password: kibanaServer.password } }; @@ -46,7 +47,8 @@ export function createServer(params = {}) { * Creates request configuration with a basic auth header */ export function authOptions() { - const authHeader = basicAuthHeader('user', 'notsecure'); + const { username, password } = kibanaUser; + const authHeader = basicAuthHeader(username, password); return set({}, 'headers.Authorization', authHeader); };