describe('for ' + name + ' Data', function () { let PersistedState; let vislibVis; let vis; let persistedState; const visLibParams = { type: 'heatmap', addLegend: true, addTooltip: true, colorsNumber: 4, colorSchema: 'Greens', setColorRange: false, percentageMode: true, invertColors: false, colorsRange: [] }; function generateVis(opts = {}) { const config = _.defaultsDeep({}, opts, visLibParams); vis = vislibVis(config); persistedState = new PersistedState(); vis.on('brush', _.noop); vis.render(data, persistedState); } beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private, $injector) { vislibVis = Private(FixturesVislibVisFixtureProvider); PersistedState = $injector.get('PersistedState'); generateVis(); })); afterEach(function () { vis.destroy(); }); it('category axes should be rendered in reverse order', () => { const renderedCategoryAxes = vis.handler.renderArray.filter(item => { return item.constructor && item.constructor.name === 'Axis' && item.axisConfig.get('type') === 'category'; }); expect(vis.handler.categoryAxes.length).to.equal(renderedCategoryAxes.length); expect(vis.handler.categoryAxes[0].axisConfig.get('id')).to.equal(renderedCategoryAxes[1].axisConfig.get('id')); expect(vis.handler.categoryAxes[1].axisConfig.get('id')).to.equal(renderedCategoryAxes[0].axisConfig.get('id')); }); describe('addSquares method', function () { it('should append rects', function () { vis.handler.charts.forEach(function (chart) { const numOfRects = chart.chartData.series.reduce((result, series) => { return result + series.values.length; }, 0); expect($(chart.chartEl).find('.series rect')).to.have.length(numOfRects); }); }); }); describe('addBarEvents method', function () { function checkChart(chart) { const rect = $(chart.chartEl).find('.series rect').get(0); return { click: !!rect.__onclick, mouseOver: !!rect.__onmouseover, // D3 brushing requires that a g element is appended that // listens for mousedown events. This g element includes // listeners, however, I was not able to test for the listener // function being present. I will need to update this test // in the future. brush: !!d3.select('.brush')[0][0] }; } it('should attach the brush if data is a set of ordered dates', function () { vis.handler.charts.forEach(function (chart) { const has = checkChart(chart); const ordered = vis.handler.data.get('ordered'); const date = Boolean(ordered && ordered.date); expect(has.brush).to.be(date); }); }); it('should attach a click event', function () { vis.handler.charts.forEach(function (chart) { const has = checkChart(chart); expect(has.click).to.be(true); }); }); it('should attach a hover event', function () { vis.handler.charts.forEach(function (chart) { const has = checkChart(chart); expect(has.mouseOver).to.be(true); }); }); }); describe('draw method', function () { it('should return a function', function () { vis.handler.charts.forEach(function (chart) { expect(_.isFunction(chart.draw())).to.be(true); }); }); it('should return a yMin and yMax', function () { vis.handler.charts.forEach(function (chart) { const yAxis = chart.handler.valueAxes[0]; const domain = yAxis.getScale().domain(); expect(domain[0]).to.not.be(undefined); expect(domain[1]).to.not.be(undefined); }); }); }); it('should define default colors', function () { expect(persistedState.get('vis.defaultColors')).to.not.be(undefined); }); it('should set custom range', function () { vis.destroy(); generateVis({ setColorRange: true, colorsRange: [{ from: 0, to: 200 }, { from: 200, to: 400 }, { from: 400, to: 500 }, { from: 500, to: Infinity }] }); const labels = vis.getLegendLabels(); expect(labels[0]).to.be('0 - 200'); expect(labels[1]).to.be('200 - 400'); expect(labels[2]).to.be('400 - 500'); expect(labels[3]).to.be('500 - Infinity'); }); it('should show correcy Y axis title', function () { expect(vis.handler.categoryAxes[1].axisConfig.get('title.text')).to.equal(''); }); });
describe('initXAxis', function () { let initXAxis; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { initXAxis = Private(AggResponsePointSeriesInitXAxisProvider); })); let baseChart = { aspects: { x: { agg: { fieldFormatter: _.constant({}), write: _.constant({ params: {} }), type: {} }, col: { title: 'label' } } } }; it('sets the xAxisFormatter if the agg is not ordered', function () { let chart = _.cloneDeep(baseChart); initXAxis(chart); expect(chart) .to.have.property('xAxisLabel', 'label') .and.have.property('xAxisFormatter', chart.aspects.x.agg.fieldFormatter()); }); it('makes the chart ordered if the agg is ordered', function () { let chart = _.cloneDeep(baseChart); chart.aspects.x.agg.type.ordered = true; initXAxis(chart); expect(chart) .to.have.property('xAxisLabel', 'label') .and.have.property('xAxisFormatter', chart.aspects.x.agg.fieldFormatter()) .and.have.property('ordered'); expect(chart.ordered) .to.be.an('object') .and.not.have.property('interval'); }); it('reads the interval param from the x agg', function () { let chart = _.cloneDeep(baseChart); chart.aspects.x.agg.type.ordered = true; chart.aspects.x.agg.write = _.constant({ params: { interval: 10 } }); initXAxis(chart); expect(chart) .to.have.property('xAxisLabel', 'label') .and.have.property('xAxisFormatter', chart.aspects.x.agg.fieldFormatter()) .and.have.property('ordered'); expect(chart.ordered) .to.be.an('object') .and.have.property('interval', 10); }); });
describe('tabifyAggResponse Integration', function () { let Vis; let Buckets; let indexPattern; let tabifyAggResponse; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private, $injector) { tabifyAggResponse = Private(AggResponseTabifyTabifyProvider); Vis = Private(VisProvider); indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); })); function normalizeIds(vis) { vis.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); }); } it('transforms a simple response properly', function () { var vis = new Vis(indexPattern, { type: 'histogram', aggs: [] }); normalizeIds(vis); var resp = tabifyAggResponse(vis, fixtures.metricOnly, { canSplit: false }); expect(resp).to.not.have.property('tables'); expect(resp).to.have.property('rows').and.property('columns'); expect(resp.rows).to.have.length(1); expect(resp.columns).to.have.length(1); expect(resp.rows[0]).to.eql([1000]); expect(resp.columns[0]).to.have.property('aggConfig', vis.aggs[0]); }); describe('transforms a complex response', function () { this.slow(1000); let vis; let avg; let ext; let src; let os; let esResp; beforeEach(function () { vis = new Vis(indexPattern, { type: 'pie', aggs: [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, { type: 'terms', schema: 'split', params: { field: 'extension' } }, { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, { type: 'terms', schema: 'segment', params: { field: 'machine.os' } } ] }); normalizeIds(vis); avg = vis.aggs[0]; ext = vis.aggs[1]; src = vis.aggs[2]; os = vis.aggs[3]; esResp = _.cloneDeep(fixtures.threeTermBuckets); // remove the buckets for css in MX esResp.aggregations.agg_2.buckets[1].agg_3.buckets[0].agg_4.buckets = []; }); // check that the root table group is formed properly, then pass // each table to expectExtensionSplit, along with the expectInnerTables() // function. function expectRootGroup(rootTableGroup, expectInnerTables) { expect(rootTableGroup).to.have.property('tables'); var tables = rootTableGroup.tables; expect(tables).to.be.an('array').and.have.length(3); expectExtensionSplit(tables[0], 'png', expectInnerTables); expectExtensionSplit(tables[1], 'css', expectInnerTables); expectExtensionSplit(tables[2], 'html', expectInnerTables); } // check that the tableGroup for the extension agg was formed properly // then call expectTable() on each table inside. it should validate that // each table is formed properly function expectExtensionSplit(tableGroup, key, expectTable) { expect(tableGroup).to.have.property('tables'); expect(tableGroup).to.have.property('aggConfig', ext); expect(tableGroup).to.have.property('key', key); expect(tableGroup.tables).to.be.an('array').and.have.length(1); tableGroup.tables.forEach(function (table) { expectTable(table, key); }); } // check that the columns of a table are formed properly function expectColumns(table, aggs) { expect(table.columns).to.be.an('array').and.have.length(aggs.length); aggs.forEach(function (agg, i) { expect(table.columns[i]).to.have.property('aggConfig', agg); }); } // check that a row has expected values function expectRow(row, asserts) { expect(row).to.be.an('array'); expect(row).to.have.length(asserts.length); asserts.forEach(function (assert, i) { assert(row[i]); }); } // check for two character country code function expectCountry(val) { expect(val).to.be.a('string'); expect(val).to.have.length(2); } // check for an empty cell function expectEmpty(val) { expect(val) .to.be(''); } // check for an OS term function expectOS(val) { expect(val) .to.match(/^(win|mac|linux)$/); } // check for something like an average bytes result function expectAvgBytes(val) { expect(val).to.be.a('number'); expect(val === 0 || val > 1000).to.be.ok(); } // create an assert that checks for an expected value function expectVal(expected) { return function (val) { expect(val).to.be(expected); }; } it('for non-hierarchical vis', function () { // the default for a non-hierarchical vis is to display // only complete rows, and only put the metrics at the end. vis.isHierarchical = _.constant(false); var tabbed = tabifyAggResponse(vis, esResp); expectRootGroup(tabbed, function expectTable(table, splitKey) { expectColumns(table, [src, os, avg]); table.rows.forEach(function (row) { if (splitKey === 'css' && row[0] === 'MX') { throw new Error('expected the MX row in the css table to be removed'); } else { expectRow(row, [ expectCountry, expectOS, expectAvgBytes ]); } }); }); }); it('for hierarchical vis, with partial rows', function () { // since we have partialRows we expect that one row will have some empty // values, and since the vis is hierarchical and we are NOT using // minimalColumns we should expect the partial row to be completely after // the existing bucket and it's metric vis.isHierarchical = _.constant(true); var tabbed = tabifyAggResponse(vis, esResp, { partialRows: true }); expectRootGroup(tabbed, function expectTable(table, splitKey) { expectColumns(table, [src, avg, os, avg]); table.rows.forEach(function (row) { if (splitKey === 'css' && row[0] === 'MX') { expectRow(row, [ expectCountry, expectAvgBytes, expectEmpty, expectEmpty ]); } else { expectRow(row, [ expectCountry, expectAvgBytes, expectOS, expectAvgBytes ]); } }); }); }); it('for hierarchical vis, with partial rows, and minimal columns', function () { // since we have partialRows we expect that one row has some empty // values, and since the vis is hierarchical and we are displaying using // minimalColumns, we should expect the partial row to have a metric at // the end vis.isHierarchical = _.constant(true); var tabbed = tabifyAggResponse(vis, esResp, { partialRows: true, minimalColumns: true }); expectRootGroup(tabbed, function expectTable(table, splitKey) { expectColumns(table, [src, os, avg]); table.rows.forEach(function (row) { if (splitKey === 'css' && row[0] === 'MX') { expectRow(row, [ expectCountry, expectEmpty, expectVal(9299) ]); } else { expectRow(row, [ expectCountry, expectOS, expectAvgBytes ]); } }); }); }); it('for non-hierarchical vis, minimal columns set to false', function () { // the reason for this test is mainly to check that setting // minimalColumns = false on a non-hierarchical vis doesn't // create metric columns after each bucket vis.isHierarchical = _.constant(false); var tabbed = tabifyAggResponse(vis, esResp, { minimalColumns: false }); expectRootGroup(tabbed, function expectTable(table, splitKey) { expectColumns(table, [src, os, avg]); table.rows.forEach(function (row) { expectRow(row, [ expectCountry, expectOS, expectAvgBytes ]); }); }); }); }); });
describe('Heatmaps', function () { beforeEach(ngMock.module('MarkerFactory')); beforeEach(ngMock.inject(function (Private) { var MarkerClass = Private(VislibVisualizationsMarkerTypesHeatmapProvider); markerLayer = createMarker(MarkerClass); })); describe('dataToHeatArray', function () { var max; beforeEach(function () { max = mapData.properties.allmax; }); it('should return an array or values for each feature', function () { var arr = markerLayer._dataToHeatArray(max); expect(arr).to.be.an('array'); expect(arr).to.have.length(mapData.features.length); }); it('should return an array item with lat, lng, metric for each feature', function () { _.times(3, function () { var arr = markerLayer._dataToHeatArray(max); var index = _.random(mapData.features.length - 1); var feature = mapData.features[index]; var featureValue = feature.properties.value; var featureArr = feature.geometry.coordinates.slice(0).concat(featureValue); expect(arr[index]).to.eql(featureArr); }); }); it('should return an array item with lat, lng, normalized metric for each feature', function () { _.times(5, function () { markerLayer._attr.heatNormalizeData = true; var arr = markerLayer._dataToHeatArray(max); var index = _.random(mapData.features.length - 1); var feature = mapData.features[index]; var featureValue = feature.properties.value / max; var featureArr = feature.geometry.coordinates.slice(0).concat(featureValue); expect(arr[index]).to.eql(featureArr); }); }); }); describe('tooltipProximity', function () { it('should return true if feature is close enough to event latlng', function () { _.times(5, function () { var feature = _.sample(mapData.features); var point = markerLayer._getLatLng(feature); var arr = markerLayer._tooltipProximity(point, feature); expect(arr).to.be(true); }); }); it('should return false if feature is not close enough to event latlng', function () { _.times(5, function () { var feature = _.sample(mapData.features); var point = L.latLng(90, -180); var arr = markerLayer._tooltipProximity(point, feature); expect(arr).to.be(false); }); }); }); describe('nearestFeature', function () { it('should return nearest geoJson feature object', function () { _.times(5, function () { var feature = _.sample(mapData.features); var point = markerLayer._getLatLng(feature); var nearestPoint = markerLayer._nearestFeature(point); expect(nearestPoint).to.equal(feature); }); }); }); describe('getLatLng', function () { it('should return a leaflet latLng object', function () { var feature = _.sample(mapData.features); var latLng = markerLayer._getLatLng(feature); var compare = L.latLng(feature.geometry.coordinates.slice(0).reverse()); expect(latLng).to.eql(compare); }); it('should memoize the result', function () { var spy = sinon.spy(L, 'latLng'); var feature = _.sample(mapData.features); markerLayer._getLatLng(feature); expect(spy.callCount).to.be(1); markerLayer._getLatLng(feature); expect(spy.callCount).to.be(1); }); }); });
describe('RegionMapsVisualizationTests', function () { let domNode; let RegionMapsVisualization; let Vis; let indexPattern; let vis; let imageComparator; const _makeJsonAjaxCallOld = ChoroplethLayer.prototype._makeJsonAjaxCall; const dummyTableGroup = { columns: [{ 'id': 'col-0', 'aggConfig': { 'id': '2', 'enabled': true, 'type': 'terms', 'schema': 'segment', 'params': { 'field': 'geo.dest', 'size': 5, 'order': 'desc', 'orderBy': '1' } }, 'title': 'geo.dest: Descending' }, { 'id': 'col-1', 'aggConfig': { 'id': '1', 'enabled': true, 'type': 'count', 'schema': 'metric', 'params': {} }, 'title': 'Count' }], rows: [ { 'col-0': 'CN', 'col-1': 26 }, { 'col-0': 'IN', 'col-1': 17 }, { 'col-0': 'US', 'col-1': 6 }, { 'col-0': 'DE', 'col-1': 4 }, { 'col-0': 'BR', 'col-1': 3 } ] }; beforeEach(ngMock.module('kibana')); let getManifestStub; beforeEach(ngMock.inject((Private, $injector) => { Vis = Private(visModule.VisProvider); RegionMapsVisualization = Private(RegionMapsVisualizationProvider); indexPattern = Private(LogstashIndexPatternStubProvider); ChoroplethLayer.prototype._makeJsonAjaxCall = async function () { //simulate network call return new Promise((resolve)=> { setTimeout(() => { resolve(worldJson); }, 10); }); }; const serviceSettings = $injector.get('serviceSettings'); getManifestStub = serviceSettings.__debugStubManifestCalls(async (url) => { //simulate network calls if (url.startsWith('https://foobar')) { return EMS_CATALOGUE; } else if (url.startsWith('https://tiles.foobar')) { return EMS_TILES; } else if (url.startsWith('https://files.foobar')) { return EMS_FILES; } }); })); afterEach(function () { ChoroplethLayer.prototype._makeJsonAjaxCall = _makeJsonAjaxCallOld; getManifestStub.removeStub(); }); describe('RegionMapVisualization - basics', function () { beforeEach(async function () { setupDOM('512px', '512px'); imageComparator = new ImageComparator(); vis = new Vis(indexPattern, { type: 'region_map' }); vis.params.bucket = { accessor: 0, }; vis.params.metric = { accessor: 1, }; vis.params.selectedJoinField = { 'name': 'iso2', 'description': 'Two letter abbreviation' }; vis.params.selectedLayer = { 'attribution': '<p><a href="http://www.naturalearthdata.com/about/terms-of-use">Made with NaturalEarth</a> | <a href="https://www.elastic.co/elastic-maps-service">Elastic Maps Service</a></p> ', 'name': 'World Countries', 'format': 'geojson', 'url': 'https://vector-staging.maps.elastic.co/blob/5715999101812736?elastic_tile_service_tos=agree&my_app_version=7.0.0-alpha1', 'fields': [{ 'name': 'iso2', 'description': 'Two letter abbreviation' }, { 'name': 'iso3', 'description': 'Three letter abbreviation' }, { 'name': 'name', 'description': 'Country name' }], 'created_at': '2017-07-31T16:00:19.996450', 'id': 5715999101812736, 'layerId': 'elastic_maps_service.World Countries' }; }); afterEach(function () { teardownDOM(); imageComparator.destroy(); }); it('should instantiate at zoom level 2', async function () { const regionMapsVisualization = new RegionMapsVisualization(domNode, vis); await regionMapsVisualization.render(dummyTableGroup, { resize: false, params: true, aggs: true, data: true, uiState: false }); const mismatchedPixels = await compareImage(initialPng); regionMapsVisualization.destroy(); expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); }); it('should update after resetting join field', async function () { const regionMapsVisualization = new RegionMapsVisualization(domNode, vis); await regionMapsVisualization.render(dummyTableGroup, { resize: false, params: true, aggs: true, data: true, uiState: false }); //this will actually create an empty image vis.params.selectedJoinField = { 'name': 'iso3', 'description': 'Three letter abbreviation' }; vis.params.isDisplayWarning = false;//so we don't get notifications await regionMapsVisualization.render(dummyTableGroup, { resize: false, params: true, aggs: false, data: false, uiState: false }); const mismatchedPixels = await compareImage(toiso3Png); regionMapsVisualization.destroy(); expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); }); it('should resize', async function () { const regionMapsVisualization = new RegionMapsVisualization(domNode, vis); await regionMapsVisualization.render(dummyTableGroup, { resize: false, params: true, aggs: true, data: true, uiState: false }); domNode.style.width = '256px'; domNode.style.height = '128px'; await regionMapsVisualization.render(dummyTableGroup, { resize: true, params: false, aggs: false, data: false, uiState: false }); const mismatchedPixelsAfterFirstResize = await compareImage(afterresizePng); domNode.style.width = '512px'; domNode.style.height = '512px'; await regionMapsVisualization.render(dummyTableGroup, { resize: true, params: false, aggs: false, data: false, uiState: false }); const mismatchedPixelsAfterSecondResize = await compareImage(initialPng); regionMapsVisualization.destroy(); expect(mismatchedPixelsAfterFirstResize).to.be.lessThan(PIXEL_DIFF); expect(mismatchedPixelsAfterSecondResize).to.be.lessThan(PIXEL_DIFF); }); it('should redo data', async function () { const regionMapsVisualization = new RegionMapsVisualization(domNode, vis); await regionMapsVisualization.render(dummyTableGroup, { resize: false, params: true, aggs: true, data: true, uiState: false }); const newTableGroup = _.cloneDeep(dummyTableGroup); newTableGroup.rows.pop();//remove one shape await regionMapsVisualization.render(newTableGroup, { resize: false, params: false, aggs: false, data: true, uiState: false }); const mismatchedPixelsAfterDataChange = await compareImage(afterdatachangePng); const anotherTableGroup = _.cloneDeep(newTableGroup); anotherTableGroup.rows.pop();//remove one shape domNode.style.width = '412px'; domNode.style.height = '112px'; await regionMapsVisualization.render(anotherTableGroup, { resize: true, params: false, aggs: false, data: true, uiState: false }); const mismatchedPixelsAfterDataChangeAndResize = await compareImage(afterdatachangeandresizePng); regionMapsVisualization.destroy(); expect(mismatchedPixelsAfterDataChange).to.be.lessThan(PIXEL_DIFF); expect(mismatchedPixelsAfterDataChangeAndResize).to.be.lessThan(PIXEL_DIFF); }); it('should redo data and color ramp', async function () { const regionMapsVisualization = new RegionMapsVisualization(domNode, vis); await regionMapsVisualization.render(dummyTableGroup, { resize: false, params: true, aggs: true, data: true, uiState: false }); const newTableGroup = _.cloneDeep(dummyTableGroup); newTableGroup.rows.pop();//remove one shape vis.params.colorSchema = 'Blues'; await regionMapsVisualization.render(newTableGroup, { resize: false, params: true, aggs: false, data: true, uiState: false }); const mismatchedPixelsAfterDataAndColorChange = await compareImage(aftercolorchangePng); regionMapsVisualization.destroy(); expect(mismatchedPixelsAfterDataAndColorChange).to.be.lessThan(PIXEL_DIFF); }); it('should zoom and center elsewhere', async function () { vis.params.mapZoom = 4; vis.params.mapCenter = [36, -85]; const regionMapsVisualization = new RegionMapsVisualization(domNode, vis); await regionMapsVisualization.render(dummyTableGroup, { resize: false, params: true, aggs: true, data: true, uiState: false }); const mismatchedPixels = await compareImage(changestartupPng); regionMapsVisualization.destroy(); expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); }); }); async function compareImage(expectedImageSource) { const elementList = domNode.querySelectorAll('canvas'); expect(elementList.length).to.equal(1); const firstCanvasOnMap = elementList[0]; return imageComparator.compareImage(firstCanvasOnMap, expectedImageSource, THRESHOLD); } function setupDOM(width, height) { domNode = document.createElement('div'); domNode.style.top = '0'; domNode.style.left = '0'; domNode.style.width = width; domNode.style.height = height; domNode.style.position = 'fixed'; domNode.style.border = '1px solid blue'; domNode.style['pointer-events'] = 'none'; document.body.appendChild(domNode); } function teardownDOM() { domNode.innerHTML = ''; document.body.removeChild(domNode); } });
describe('Vislib xAxis Class Test Suite', function () { let Axis; let persistedState; let xAxis; let el; let fixture; let VisConfig; const data = { hits: 621, ordered: { date: true, interval: 30000, max: 1408734982458, min: 1408734082458 }, series: [ { label: 'Count', values: [ { x: 1408734060000, y: 8 }, { x: 1408734090000, y: 23 }, { x: 1408734120000, y: 30 }, { x: 1408734150000, y: 28 }, { x: 1408734180000, y: 36 }, { x: 1408734210000, y: 30 }, { x: 1408734240000, y: 26 }, { x: 1408734270000, y: 22 }, { x: 1408734300000, y: 29 }, { x: 1408734330000, y: 24 } ] } ], xAxisFormatter: function (thing) { return new Date(thing); }, xAxisLabel: 'Date Histogram', yAxisLabel: 'Count' }; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private, $injector) { persistedState = new ($injector.get('PersistedState'))(); Axis = Private(VislibLibAxisProvider); VisConfig = Private(VislibVisConfig); el = d3.select('body').append('div') .attr('class', 'x-axis-wrapper') .style('height', '40px'); fixture = el.append('div') .attr('class', 'x-axis-div'); const visConfig = new VisConfig({ type: 'histogram' }, data, persistedState, $('.x-axis-div')[0]); xAxis = new Axis(visConfig, { type: 'category', id: 'CategoryAxis-1' }); })); afterEach(function () { fixture.remove(); el.remove(); }); describe('render Method', function () { beforeEach(function () { xAxis.render(); }); it('should append an svg to div', function () { expect(el.selectAll('svg').length).to.be(1); }); it('should append a g element to the svg', function () { expect(el.selectAll('svg').select('g').length).to.be(1); }); it('should append ticks with text', function () { expect(!!el.selectAll('svg').selectAll('.tick text')).to.be(true); }); }); describe('getScale, getDomain, getTimeDomain, and getRange Methods', function () { let timeScale; let width; let range; beforeEach(function () { width = $('.x-axis-div').width(); xAxis.getAxis(width); timeScale = xAxis.getScale(); range = xAxis.axisScale.getRange(width); }); it('should return a function', function () { expect(_.isFunction(timeScale)).to.be(true); }); it('should return the correct domain', function () { expect(_.isDate(timeScale.domain()[0])).to.be(true); expect(_.isDate(timeScale.domain()[1])).to.be(true); }); it('should return the min and max dates', function () { expect(timeScale.domain()[0].toDateString()).to.be(new Date(1408734060000).toDateString()); expect(timeScale.domain()[1].toDateString()).to.be(new Date(1408734330000).toDateString()); }); it('should return the correct range', function () { expect(range[0]).to.be(0); expect(range[1]).to.be(width); }); }); describe('getOrdinalDomain Method', function () { let ordinalScale; let ordinalDomain; let width; beforeEach(function () { width = $('.x-axis-div').width(); xAxis.ordered = null; xAxis.axisConfig.ordered = null; xAxis.getAxis(width); ordinalScale = xAxis.getScale(); ordinalDomain = ordinalScale.domain(['this', 'should', 'be', 'an', 'array']); }); it('should return an ordinal scale', function () { expect(ordinalDomain.domain()[0]).to.be('this'); expect(ordinalDomain.domain()[4]).to.be('array'); }); it('should return an array of values', function () { expect(_.isArray(ordinalDomain.domain())).to.be(true); }); }); describe('getXScale Method', function () { let width; let xScale; beforeEach(function () { width = $('.x-axis-div').width(); xAxis.getAxis(width); xScale = xAxis.getScale(); }); it('should return a function', function () { expect(_.isFunction(xScale)).to.be(true); }); it('should return a domain', function () { expect(_.isDate(xScale.domain()[0])).to.be(true); expect(_.isDate(xScale.domain()[1])).to.be(true); }); it('should return a range', function () { expect(xScale.range()[0]).to.be(0); expect(xScale.range()[1]).to.be(width); }); }); describe('getXAxis Method', function () { let width; beforeEach(function () { width = $('.x-axis-div').width(); xAxis.getAxis(width); }); it('should create an getScale function on the xAxis class', function () { expect(_.isFunction(xAxis.getScale())).to.be(true); }); }); describe('draw Method', function () { it('should be a function', function () { expect(_.isFunction(xAxis.draw())).to.be(true); }); }); });
describe('AggParams class', function () { let AggParams; let BaseAggParam; let FieldAggParam; let OptionedAggParam; let RegexAggParam; beforeEach(ngMock.module('kibana')); // stub out the param classes before we get the AggParams beforeEach(ngMock.inject(require('./utils/_stub_agg_params'))); // fetch out deps beforeEach(ngMock.inject(function (Private) { AggParams = Private(AggTypesAggParamsProvider); BaseAggParam = Private(AggTypesParamTypesBaseProvider); FieldAggParam = Private(AggTypesParamTypesFieldProvider); OptionedAggParam = Private(AggTypesParamTypesOptionedProvider); RegexAggParam = Private(AggTypesParamTypesRegexProvider); })); describe('constructor args', function () { it('accepts an array of param defs', function () { const params = [ { name: 'one' }, { name: 'two' } ]; const aggParams = new AggParams(params); expect(aggParams).to.have.length(params.length); expect(aggParams).to.be.an(Array); expect(aggParams.byName).to.have.keys(['one', 'two']); }); }); describe('AggParam creation', function () { it('Uses the FieldAggParam class for params with the name "field"', function () { const params = [ { name: 'field' } ]; const aggParams = new AggParams(params); expect(aggParams).to.have.length(params.length); expect(aggParams[0]).to.be.a(FieldAggParam); }); it('Uses the OptionedAggParam class for params of type "optioned"', function () { const params = [ { name: 'interval', type: 'optioned' } ]; const aggParams = new AggParams(params); expect(aggParams).to.have.length(params.length); expect(aggParams[0]).to.be.a(OptionedAggParam); }); it('Uses the RegexAggParam class for params of type "regex"', function () { const params = [ { name: 'exclude', type: 'regex' } ]; const aggParams = new AggParams(params); expect(aggParams).to.have.length(params.length); expect(aggParams[0]).to.be.a(RegexAggParam); }); it('Always converts the params to a BaseAggParam', function () { const params = [ { name: 'height', editor: '<blink>high</blink>' }, { name: 'weight', editor: '<blink>big</blink>' }, { name: 'waist', editor: '<blink>small</blink>' } ]; const aggParams = new AggParams(params); expect(BaseAggParam).to.have.property('callCount', params.length); expect(FieldAggParam).to.have.property('callCount', 0); expect(OptionedAggParam).to.have.property('callCount', 0); expect(aggParams).to.have.length(params.length); aggParams.forEach(function (aggParam) { expect(aggParam).to.be.a(BaseAggParam); }); }); }); });
describe('mapScript()', function () { let mapScript; let $rootScope; beforeEach(ngMock.module( 'kibana', 'kibana/courier', function ($provide) { $provide.service('courier', require('fixtures/mock_courier')); } )); beforeEach(ngMock.inject(function (Private, _$rootScope_) { $rootScope = _$rootScope_; mapScript = Private(FilterBarLibMapScriptProvider); })); it('should return the key and value for matching filters', function (done) { const filter = { meta: { index: 'logstash-*', field: 'script number' }, script: { script: { inline: 'doc["script number"].value * 5', params: { value: 35 } } } }; mapScript(filter).then(function (result) { expect(result).to.have.property('key', 'script number'); expect(result).to.have.property('value', '35'); done(); }); $rootScope.$apply(); }); it('should return undefined for none matching', function (done) { const filter = { meta: { index: 'logstash-*' }, query: { query_string: { query: 'foo:bar' } } }; mapScript(filter).catch(function (result) { expect(result).to.be(filter); done(); }); $rootScope.$apply(); }); it('should return a value for a range/histogram filter from a scripted field', (done) => { const filter = { meta: { index: 'logstash-*', formattedValue: '1,000.00 to 2,000.00', field: 'script number' }, script: { script: { params: { gte: 1000, lt: 2000, value: '>=1,000.00 <2,000.00' } } } }; mapScript(filter).then((result) => { expect(result).to.have.property('value', filter.meta.formattedValue); done(); }); $rootScope.$apply(); }); });
describe('buildHierarchicalData', function () { let Vis; let indexPattern; let responseHandler; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { Vis = Private(VisProvider); indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); responseHandler = Private(VislibSlicesResponseHandlerProvider).handler; })); const buildHierarchicalData = async (aggs, response) => { const vis = new Vis(indexPattern, { type: 'histogram', aggs: aggs }); vis.isHierarchical = () => true; const data = tabifyAggResponse(vis.aggs, response, { metricsAtAllLevels: true }); return await responseHandler(data); }; describe('metric only', function () { let results; beforeEach(async function () { const aggs = [{ id: 'agg_1', schema: 'metric', type: 'avg', params: { field: 'bytes', } }]; results = await buildHierarchicalData(aggs, fixtures.metricOnly); }); it('should set the slices with one child to a consistent label', function () { const checkLabel = 'Average bytes'; expect(results).to.have.property('slices'); expect(results.slices).to.have.property('children'); expect(results.slices.children).to.have.length(1); expect(results.slices.children[0]).to.have.property('name', checkLabel); expect(results.slices.children[0]).to.have.property('size', 412032); expect(results).to.have.property('names'); expect(results.names).to.eql([checkLabel]); expect(results).to.have.property('raw'); expect(results.raw).to.have.property('rows'); expect(results.raw.rows).to.have.length(1); }); }); describe('rows and columns', function () { let results; it('should set the rows', async function () { const aggs = [{ id: 'agg_2', type: 'terms', schema: 'split', params: { field: 'extension', } }, { id: 'agg_3', type: 'terms', schema: 'group', params: { field: 'geo.src', } }]; results = await buildHierarchicalData(aggs, fixtures.threeTermBuckets); expect(results).to.have.property('rows'); }); it('should set the columns', async function () { const aggs = [{ id: 'agg_2', type: 'terms', schema: 'split', params: { row: false, field: 'extension', } }, { id: 'agg_3', type: 'terms', schema: 'group', params: { field: 'geo.src', } }]; results = await buildHierarchicalData(aggs, fixtures.threeTermBuckets); expect(results).to.have.property('columns'); }); }); describe('threeTermBuckets', function () { let results; beforeEach(async function () { const aggs = [{ id: 'agg_1', type: 'avg', schema: 'metric', params: { field: 'bytes', } }, { id: 'agg_2', type: 'terms', schema: 'split', params: { field: 'extension', } }, { id: 'agg_3', type: 'terms', schema: 'group', params: { field: 'geo.src', } }, { id: 'agg_4', type: 'terms', schema: 'group', params: { field: 'machine.os', } }]; results = await buildHierarchicalData(aggs, fixtures.threeTermBuckets); }); it('should set the hits attribute for the results', function () { expect(results).to.have.property('rows'); _.each(results.rows, function (item) { expect(item).to.have.property('names'); expect(item).to.have.property('slices'); expect(item.slices).to.have.property('children'); }); }); it('should set the parent of the first item in the split', function () { expect(results).to.have.property('rows'); expect(results.rows).to.have.length(3); expect(results.rows[0]).to.have.property('slices'); expect(results.rows[0].slices).to.have.property('children'); expect(results.rows[0].slices.children).to.have.length(2); expect(results.rows[0].slices.children[0]).to.have.property('aggConfigResult'); expect(results.rows[0].slices.children[0].aggConfigResult.$parent.$parent).to.have.property('key', 'png'); }); }); describe('oneHistogramBucket', function () { let results; beforeEach(async function () { const aggs = [{ id: 'agg_2', type: 'histogram', schema: 'group', params: { field: 'bytes', interval: 8192 } }]; results = await buildHierarchicalData(aggs, fixtures.oneHistogramBucket); }); it('should set the hits attribute for the results', function () { expect(results).to.have.property('slices'); expect(results.slices).to.property('children'); expect(results).to.have.property('names'); expect(results.names).to.have.length(6); expect(results).to.have.property('raw'); }); }); describe('oneRangeBucket', function () { let results; beforeEach(async function () { const aggs = [{ id: 'agg_2', type: 'range', schema: 'group', params: { field: 'bytes', } }]; results = await buildHierarchicalData(aggs, fixtures.oneRangeBucket); }); it('should set the hits attribute for the results', function () { expect(results).to.have.property('slices'); expect(results.slices).to.property('children'); expect(results).to.have.property('names'); expect(results.names).to.have.length(2); expect(results).to.have.property('raw'); }); }); describe('oneFilterBucket', function () { let results; beforeEach(async function () { const aggs = [{ id: 'agg_2', type: 'filters', schema: 'group', params: { field: 'geo.src', filters: [ { label: 'type:apache' }, { label: 'type:nginx' } ] } }]; results = await buildHierarchicalData(aggs, fixtures.oneFilterBucket); }); it('should set the hits attribute for the results', function () { expect(results).to.have.property('slices'); expect(results).to.have.property('names'); expect(results.names).to.have.length(2); expect(results).to.have.property('raw'); }); }); });
describe('interval.toIndexList()', function () { let intervals; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { intervals = Private(IndexPatternsIntervalsProvider); })); it('should return correct indices for hourly [logstash-]YYYY.MM.DD.HH', function () { var start = moment.utc('2014-01-01T07:00:00Z'); var end = moment.utc('2014-01-01T08:30:00Z'); var interval = { name: 'hours', startOf: 'hour', display: 'Hourly' }; var list = intervals.toIndexList('[logstash-]YYYY.MM.DD.HH', interval, start, end); expect(list).to.eql([ { index: 'logstash-2014.01.01.07', min: moment.utc('2014-01-01T07:00:00').valueOf(), max: moment.utc('2014-01-01T07:59:59.999').valueOf(), }, { index: 'logstash-2014.01.01.08', min: moment.utc('2014-01-01T08:00:00').valueOf(), max: moment.utc('2014-01-01T08:59:59.999').valueOf(), } ]); }); it('should return correct indices for daily [logstash-]YYYY.MM.DD', function () { var start = moment(1418244231248); var end = moment(1418849261281); var interval = { name: 'days', startOf: 'day', display: 'Daily' }; var list = intervals.toIndexList('[logstash-]YYYY.MM.DD', interval, start, end); expect(list).to.eql([ { index: 'logstash-2014.12.10', min: moment.utc('2014-12-10T00:00:00').valueOf(), max: moment.utc('2014-12-10T23:59:59.999').valueOf(), }, { index: 'logstash-2014.12.11', min: moment.utc('2014-12-11T00:00:00').valueOf(), max: moment.utc('2014-12-11T23:59:59.999').valueOf(), }, { index: 'logstash-2014.12.12', min: moment.utc('2014-12-12T00:00:00').valueOf(), max: moment.utc('2014-12-12T23:59:59.999').valueOf(), }, { index: 'logstash-2014.12.13', min: moment.utc('2014-12-13T00:00:00').valueOf(), max: moment.utc('2014-12-13T23:59:59.999').valueOf(), }, { index: 'logstash-2014.12.14', min: moment.utc('2014-12-14T00:00:00').valueOf(), max: moment.utc('2014-12-14T23:59:59.999').valueOf(), }, { index: 'logstash-2014.12.15', min: moment.utc('2014-12-15T00:00:00').valueOf(), max: moment.utc('2014-12-15T23:59:59.999').valueOf(), }, { index: 'logstash-2014.12.16', min: moment.utc('2014-12-16T00:00:00').valueOf(), max: moment.utc('2014-12-16T23:59:59.999').valueOf(), }, { index: 'logstash-2014.12.17', min: moment.utc('2014-12-17T00:00:00').valueOf(), max: moment.utc('2014-12-17T23:59:59.999').valueOf(), }, ]); }); it('should return correct indices for monthly [logstash-]YYYY.MM', function () { var start = moment.utc('2014-12-01'); var end = moment.utc('2015-02-01'); var interval = { name: 'months', startOf: 'month', display: 'Monthly' }; var list = intervals.toIndexList('[logstash-]YYYY.MM', interval, start, end); expect(list).to.eql([ { index: 'logstash-2014.12', min: moment.utc(0).year(2014).month(11).valueOf(), max: moment.utc(0).year(2015).month(0).subtract(1, 'ms').valueOf(), }, { index: 'logstash-2015.01', min: moment.utc(0).year(2015).month(0).valueOf(), max: moment.utc(0).year(2015).month(1).subtract(1, 'ms').valueOf(), }, { index: 'logstash-2015.02', min: moment.utc(0).year(2015).month(1).valueOf(), max: moment.utc(0).year(2015).month(2).subtract(1, 'ms').valueOf(), }, ]); }); it('should return correct indices for yearly [logstash-]YYYY', function () { var start = moment.utc('2014-12-01'); var end = moment.utc('2015-02-01'); var interval = { name: 'years', startOf: 'year', display: 'Yearly' }; var list = intervals.toIndexList('[logstash-]YYYY', interval, start, end); expect(list).to.eql([ { index: 'logstash-2014', min: moment.utc(0).year(2014).valueOf(), max: moment.utc(0).year(2015).subtract(1, 'ms').valueOf(), }, { index: 'logstash-2015', min: moment.utc(0).year(2015).valueOf(), max: moment.utc(0).year(2016).subtract(1, 'ms').valueOf(), }, ]); }); context('with sortDirection=asc', function () { it('returns values in ascending order', function () { var start = moment.utc('2014-12-01'); var end = moment.utc('2015-02-01'); var interval = { name: 'years', startOf: 'year', display: 'Yearly' }; var list = intervals.toIndexList('[logstash-]YYYY', interval, start, end, 'asc'); expect(list).to.eql([ { index: 'logstash-2014', min: moment.utc(0).year(2014).valueOf(), max: moment.utc(0).year(2015).subtract(1, 'ms').valueOf(), }, { index: 'logstash-2015', min: moment.utc(0).year(2015).valueOf(), max: moment.utc(0).year(2016).subtract(1, 'ms').valueOf(), }, ]); }); }); context('with sortDirection=desc', function () { it('returns values in descending order', function () { var start = moment.utc('2014-12-01'); var end = moment.utc('2015-02-01'); var interval = { name: 'years', startOf: 'year', display: 'Yearly' }; var list = intervals.toIndexList('[logstash-]YYYY', interval, start, end, 'desc'); expect(list).to.eql([ { index: 'logstash-2015', min: moment.utc(0).year(2015).valueOf(), max: moment.utc(0).year(2016).subtract(1, 'ms').valueOf(), }, { index: 'logstash-2014', min: moment.utc(0).year(2014).valueOf(), max: moment.utc(0).year(2015).subtract(1, 'ms').valueOf(), }, ]); }); }); });
describe('Vislib yAxis Class Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { Data = Private(VislibLibDataProvider); persistedState = new (Private(PersistedStatePersistedStateProvider))(); YAxis = Private(VislibLibYAxisProvider); expect($('.y-axis-wrapper')).to.have.length(0); })); afterEach(function () { el.remove(); yAxisDiv.remove(); }); describe('render Method', function () { beforeEach(function () { createData(defaultGraphData); expect(d3.select(yAxis.el).selectAll('.y-axis-div')).to.have.length(1); yAxis.render(); }); it('should append an svg to div', function () { expect(el.selectAll('svg').length).to.be(1); }); it('should append a g element to the svg', function () { expect(el.selectAll('svg').select('g').length).to.be(1); }); it('should append ticks with text', function () { expect(!!el.selectAll('svg').selectAll('.tick text')).to.be(true); }); }); describe('getYScale Method', function () { let yScale; let graphData; let domain; var height = 50; function checkDomain(min, max) { var domain = yScale.domain(); expect(domain[0]).to.be.lessThan(min + 1); expect(domain[1]).to.be.greaterThan(max - 1); return domain; } function checkRange() { expect(yScale.range()[0]).to.be(height); expect(yScale.range()[1]).to.be(0); } describe('API', function () { beforeEach(function () { createData(defaultGraphData); yScale = yAxis.getYScale(height); }); it('should return a function', function () { expect(_.isFunction(yScale)).to.be(true); }); }); describe('should return log values', function () { let domain; let extents; it('should return 1', function () { yAxis._attr.scale = 'log'; extents = [0, 400]; domain = yAxis._getExtents(extents); // Log scales have a yMin value of 1 expect(domain[0]).to.be(1); }); }); describe('positive values', function () { beforeEach(function () { graphData = defaultGraphData; createData(graphData); yScale = yAxis.getYScale(height); }); it('should have domain between 0 and max value', function () { var min = 0; var max = _.max(_.flattenDeep(graphData)); var domain = checkDomain(min, max); expect(domain[1]).to.be.greaterThan(0); checkRange(); }); }); describe('negative values', function () { beforeEach(function () { graphData = [ [ -8, -23, -30, -28, -36, -30, -26, -22, -29, -24 ], [ -22, -8, -30, -4, 0, 0, -3, -22, -14, -24 ] ]; createData(graphData); yScale = yAxis.getYScale(height); }); it('should have domain between min value and 0', function () { var min = _.min(_.flattenDeep(graphData)); var max = 0; var domain = checkDomain(min, max); expect(domain[0]).to.be.lessThan(0); checkRange(); }); }); describe('positive and negative values', function () { beforeEach(function () { graphData = [ [ 8, 23, 30, 28, 36, 30, 26, 22, 29, 24 ], [ 22, 8, -30, -4, 0, 0, 3, -22, 14, 24 ] ]; createData(graphData); yScale = yAxis.getYScale(height); }); it('should have domain between min and max values', function () { var min = _.min(_.flattenDeep(graphData)); var max = _.max(_.flattenDeep(graphData)); var domain = checkDomain(min, max); expect(domain[0]).to.be.lessThan(0); expect(domain[1]).to.be.greaterThan(0); checkRange(); }); }); describe('validate user defined values', function () { beforeEach(function () { yAxis._attr.mode = 'stacked'; yAxis._attr.setYExtents = false; yAxis._attr.yAxis = {}; }); it('should throw a NaN error', function () { var min = 'Not a number'; var max = 12; expect(function () { yAxis._validateUserExtents(min, max); }).to.throwError(); }); it('should return a decimal value', function () { yAxis._attr.mode = 'percentage'; yAxis._attr.setYExtents = true; domain = []; domain[0] = yAxis._attr.yAxis.min = 20; domain[1] = yAxis._attr.yAxis.max = 80; var newDomain = yAxis._validateUserExtents(domain); expect(newDomain[0]).to.be(domain[0] / 100); expect(newDomain[1]).to.be(domain[1] / 100); }); it('should return the user defined value', function () { domain = [20, 50]; var newDomain = yAxis._validateUserExtents(domain); expect(newDomain[0]).to.be(domain[0]); expect(newDomain[1]).to.be(domain[1]); }); }); describe('should throw an error when', function () { it('min === max', function () { var min = 12; var max = 12; expect(function () { yAxis._validateAxisExtents(min, max); }).to.throwError(); }); it('min > max', function () { var min = 30; var max = 10; expect(function () { yAxis._validateAxisExtents(min, max); }).to.throwError(); }); }); }); describe('getScaleType method', function () { var fnNames = ['linear', 'log', 'square root']; it('should return a function', function () { fnNames.forEach(function (fnName) { expect(yAxis._getScaleType(fnName)).to.be.a(Function); }); // if no value is provided to the function, scale should default to a linear scale expect(yAxis._getScaleType()).to.be.a(Function); }); it('should throw an error if function name is undefined', function () { expect(function () { yAxis._getScaleType('square'); }).to.throwError(); }); }); describe('_logDomain method', function () { it('should throw an error', function () { expect(function () { yAxis._logDomain(-10, -5); }).to.throwError(); expect(function () { yAxis._logDomain(-10, 5); }).to.throwError(); expect(function () { yAxis._logDomain(0, -5); }).to.throwError(); }); it('should return a yMin value of 1', function () { var yMin = yAxis._logDomain(0, 200)[0]; expect(yMin).to.be(1); }); }); describe('getYAxis method', function () { let mode; let yMax; let yScale; beforeEach(function () { createData(defaultGraphData); mode = yAxis._attr.mode; yMax = yAxis.yMax; yScale = yAxis.getYScale; }); afterEach(function () { yAxis._attr.mode = mode; yAxis.yMax = yMax; yAxis.getYScale = yScale; }); it('should use percentage format for percentages', function () { yAxis._attr.mode = 'percentage'; var tickFormat = yAxis.getYAxis().tickFormat(); expect(tickFormat(1)).to.be('100%'); }); it('should use decimal format for small values', function () { yAxis.yMax = 1; var tickFormat = yAxis.getYAxis().tickFormat(); expect(tickFormat(0.8)).to.be('0.8'); }); it('should throw an error if yScale is NaN', function () { yAxis.getYScale = function () { return NaN; }; expect(function () { yAxis.getYAxis(); }).to.throwError(); }); }); describe('draw Method', function () { beforeEach(function () { createData(defaultGraphData); }); it('should be a function', function () { expect(_.isFunction(yAxis.draw())).to.be(true); }); }); describe('tickScale Method', function () { beforeEach(function () { createData(defaultGraphData); }); it('should return the correct number of ticks', function () { expect(yAxis.tickScale(1000)).to.be(11); expect(yAxis.tickScale(40)).to.be(3); expect(yAxis.tickScale(20)).to.be(0); }); }); describe('#tickFormat()', function () { var formatter = function () {}; it('returns a basic number formatter by default', function () { var yAxis = buildYAxis(); expect(yAxis.tickFormat()).to.not.be(formatter); expect(yAxis.tickFormat()(1)).to.be('1'); }); it('returns the yAxisFormatter when passed', function () { var yAxis = buildYAxis({ yAxisFormatter: formatter }); expect(yAxis.tickFormat()).to.be(formatter); }); it('returns a percentage formatter when the vis is in percentage mode', function () { var yAxis = buildYAxis({ yAxisFormatter: formatter, _attr: { mode: 'percentage' } }); expect(yAxis.tickFormat()).to.not.be(formatter); expect(yAxis.tickFormat()(1)).to.be('100%'); }); }); });
describe('Vislib Column Layout Test Suite', function () { let layoutType; let columnLayout; let el; const data = { hits: 621, ordered: { date: true, interval: 30000, max: 1408734982458, min: 1408734082458 }, series: [ { label: 'Count', values: [ { x: 1408734060000, y: 8 }, { x: 1408734090000, y: 23 }, { x: 1408734120000, y: 30 }, { x: 1408734150000, y: 28 }, { x: 1408734180000, y: 36 }, { x: 1408734210000, y: 30 }, { x: 1408734240000, y: 26 }, { x: 1408734270000, y: 22 }, { x: 1408734300000, y: 29 }, { x: 1408734330000, y: 24 } ] } ], xAxisLabel: 'Date Histogram', yAxisLabel: 'Count' }; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { layoutType = Private(VislibLibLayoutLayoutTypesProvider); el = d3.select('body').append('div').attr('class', 'visualization'); columnLayout = layoutType.point_series(el, data); })); afterEach(function () { el.remove(); }); it('should return an array of objects', function () { expect(_.isArray(columnLayout)).to.be(true); expect(_.isObject(columnLayout[0])).to.be(true); }); it('should throw an error when the wrong number or no arguments provided', function () { expect(function () { layoutType.point_series(el); }).to.throwError(); }); });
describe('Vislib Vis Type', function () { let VislibVisType; const visConfig = { name: 'test', title: 'test', description: 'test', icon: 'test', visConfig: { component: 'test' }, type: { visConfig: { component: 'test' } } }; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { VislibVisType = Private(VislibVisTypeProvider); })); describe('initialization', () => { it('should set the vislib response handler if not set', () => { const visType = new VislibVisType(visConfig); expect(visType.responseHandler).to.equal('vislib_series'); }); it('should not change response handler if its already set', () => { visConfig.responseHandler = 'none'; const visType = new VislibVisType(visConfig); expect(visType.responseHandler).to.equal('none'); }); it('creates vislib controller', () => { visConfig.responseHandler = 'none'; const visType = new VislibVisType(visConfig); expect(visType.visualization).to.not.be.undefined; }); }); describe('controller', function () { it('constructor sets vis and element properties', () => { visConfig.responseHandler = 'none'; const visType = new VislibVisType(visConfig); const Vis = visType.visualization; const vis = new Vis(window.document.body, {}); expect(vis.el).to.not.be.undefined; expect(vis.vis).to.not.be.undefined; }); }); describe('render method', () => { let vis; beforeEach(() => { visConfig.responseHandler = 'none'; const visType = new VislibVisType(visConfig); const Vis = visType.visualization; vis = new Vis(window.document.body, { params: {} }); }); it('rejects if response is not provided', () => { vis.render().then(() => { expect('promise was not rejected').to.equal(false); }).catch(() => {}); }); it('creates new vislib vis', () => { vis.render({}); expect(vis.vis.vislibVis).to.not.be.undefined; }); }); describe('destroy method', () => { let vis; beforeEach(() => { visConfig.responseHandler = 'none'; const visType = new VislibVisType(visConfig); const Vis = visType.visualization; vis = new Vis(window.document.body, { params: {} }); }); it('destroys vislib vis', () => { vis.render({}).then(() => { vis.destroy(); expect(vis.vis.vislibVis).to.be.undefined; }); }); }); });
describe('type normalizer (castMappingType)', function () { let fn; let fields; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private, $injector) { fn = Private(IndexPatternsCastMappingTypeProvider); })); it('should be a function', function () { expect(fn).to.be.a(Function); }); it('should have a types property', function () { expect(fn).to.have.property('types'); }); it('should cast numeric types to "number"', function () { var types = [ 'float', 'double', 'integer', 'long', 'short', 'byte', 'token_count' ]; _.each(types, function (type) { expect(fn(type)).to.be('number'); }); }); it('should treat non-numeric known types as what they are', function () { var types = [ 'date', 'boolean', 'ip', 'attachment', 'geo_point', 'geo_shape', 'murmur3', 'string' ]; _.each(types, function (type) { expect(fn(type)).to.be(type); }); }); it('should cast text and keyword types to "string"', function () { var types = [ 'keyword', 'text' ]; _.each(types, function (type) { expect(fn(type)).to.be('string'); }); }); it('should treat everything else as a string', function () { expect(fn('fooTypeIsNotReal')).to.be('string'); }); });
describe('render_directive', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function ($injector) { $rootScope = $injector.get('$rootScope'); $compile = $injector.get('$compile'); init = function init(markup = '', definition = {}) { const $parentScope = $rootScope; // create the markup const $elem = angular.element('<render-directive>'); $elem.html(markup); if (definition !== null) { $parentScope.definition = definition; $elem.attr('definition', 'definition'); } // compile the directive $compile($elem)($parentScope); $parentScope.$apply(); const $directiveScope = $elem.isolateScope(); return { $parentScope, $directiveScope, $elem }; }; })); describe('directive requirements', function () { it('should throw if not given a definition', function () { expect(() => init('', null)).to.throwException(/must have a definition/); }); }); describe('rendering with definition', function () { it('should call link method', function () { const markup = '<p>hello world</p>'; const definition = { link: sinon.stub(), }; init(markup, definition); sinon.assert.callCount(definition.link, 1); }); it('should call controller method', function () { const markup = '<p>hello world</p>'; const definition = { controller: sinon.stub(), }; init(markup, definition); sinon.assert.callCount(definition.controller, 1); }); }); describe('definition scope binding', function () { it('should accept two-way, attribute, and expression binding directives', function () { const $el = angular.element(` <render-directive definition="definition" two-way-prop="parentTwoWay" attr="Simple Attribute" expr="parentExpression()" > {{two}},{{attr}},{{expr()}} </render-directive> `); const $parentScope = $rootScope.$new(); $parentScope.definition = { scope: { two: '=twoWayProp', attr: '@', expr: '&expr' } }; $parentScope.parentTwoWay = true; $parentScope.parentExpression = function () { return !$parentScope.parentTwoWay; }; $compile($el)($parentScope); $parentScope.$apply(); expect($el.text().trim()).to.eql('true,Simple Attribute,false'); $parentScope.parentTwoWay = false; $parentScope.$apply(); expect($el.text().trim()).to.eql('false,Simple Attribute,true'); }); }); });
describe('Promise service', () => { let Promise; let $rootScope; const sandbox = sinon.createSandbox(); function tick(ms = 0) { sandbox.clock.tick(ms); // Ugly, but necessary for promises to resolve: https://github.com/angular/angular.js/issues/12555 $rootScope.$apply(); } beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(($injector) => { sandbox.useFakeTimers(); Promise = $injector.get('Promise'); $rootScope = $injector.get('$rootScope'); })); afterEach(() => sandbox.restore()); describe('Constructor', () => { it('provides resolve and reject function', () => { const executor = sinon.stub(); new Promise(executor); sinon.assert.calledOnce(executor); sinon.assert.calledWithExactly(executor, sinon.match.func, sinon.match.func); }); }); it('Promise.resolve', () => { const onResolve = sinon.stub(); Promise.resolve(true).then(onResolve); tick(); sinon.assert.calledOnce(onResolve); sinon.assert.calledWithExactly(onResolve, true); }); describe('Promise.fromNode', () => { it('creates a callback that controls a promise', () => { const callback = sinon.stub(); Promise.fromNode(callback); tick(); sinon.assert.calledOnce(callback); sinon.assert.calledWithExactly(callback, sinon.match.func); }); it('rejects if the callback receives an error', () => { const err = new Error(); const onReject = sinon.stub(); Promise.fromNode(sinon.stub().yields(err)).catch(onReject); tick(); sinon.assert.calledOnce(onReject); sinon.assert.calledWithExactly(onReject, sinon.match.same(err)); }); it('resolves with the second argument', () => { const result = {}; const onResolve = sinon.stub(); Promise.fromNode(sinon.stub().yields(null, result)).then(onResolve); tick(); sinon.assert.calledOnce(onResolve); sinon.assert.calledWithExactly(onResolve, sinon.match.same(result)); }); it('resolves with an array if multiple arguments are received', () => { const result1 = {}; const result2 = {}; const onResolve = sinon.stub(); Promise.fromNode(sinon.stub().yields(null, result1, result2)).then(onResolve); tick(); sinon.assert.calledOnce(onResolve); sinon.assert.calledWithExactly( onResolve, [sinon.match.same(result1), sinon.match.same(result2)] ); }); it('resolves with an array if multiple undefined are received', () => { const onResolve = sinon.stub(); Promise.fromNode(sinon.stub().yields(null, undefined, undefined)).then(onResolve); tick(); sinon.assert.calledOnce(onResolve); sinon.assert.calledWithExactly(onResolve, [undefined, undefined]); }); }); describe('Promise.race()', () => { it(`resolves with the first resolved promise's value`, () => { const p1 = new Promise(resolve => setTimeout(resolve, 100, 1)); const p2 = new Promise(resolve => setTimeout(resolve, 200, 2)); const onResolve = sinon.stub(); Promise.race([p1, p2]).then(onResolve); tick(200); sinon.assert.calledOnce(onResolve); sinon.assert.calledWithExactly(onResolve, 1); }); it(`rejects with the first rejected promise's rejection reason`, () => { const p1Error = new Error('1'); const p1 = new Promise((r, reject) => setTimeout(reject, 200, p1Error)); const p2Error = new Error('2'); const p2 = new Promise((r, reject) => setTimeout(reject, 100, p2Error)); const onReject = sinon.stub(); Promise.race([p1, p2]).catch(onReject); tick(200); sinon.assert.calledOnce(onReject); sinon.assert.calledWithExactly(onReject, sinon.match.same(p2Error)); }); it('does not wait for subsequent promises to resolve/reject', () => { const onP1Resolve = sinon.stub(); const p1 = new Promise(resolve => setTimeout(resolve, 100)).then(onP1Resolve); const onP2Resolve = sinon.stub(); const p2 = new Promise(resolve => setTimeout(resolve, 101)).then(onP2Resolve); const onResolve = sinon.stub(); Promise.race([p1, p2]).then(onResolve); tick(100); sinon.assert.calledOnce(onResolve); sinon.assert.calledOnce(onP1Resolve); sinon.assert.callOrder(onP1Resolve, onResolve); sinon.assert.notCalled(onP2Resolve); }); it('allows non-promises in the array', () => { const onResolve = sinon.stub(); Promise.race([1, 2, 3]).then(onResolve); tick(); sinon.assert.calledOnce(onResolve); sinon.assert.calledWithExactly(onResolve, 1); }); describe('argument is undefined', () => { it('rejects the promise', () => { const football = {}; const onReject = sinon.stub(); Promise.race().catch(() => football).then(onReject); tick(); sinon.assert.calledOnce(onReject); sinon.assert.calledWithExactly(onReject, sinon.match.same(football)); }); }); describe('argument is a string', () => { it(`resolves with the first character`, () => { const onResolve = sinon.stub(); Promise.race('abc').then(onResolve); tick(); sinon.assert.calledOnce(onResolve); sinon.assert.calledWithExactly(onResolve, 'a'); }); }); describe('argument is a non-iterable object', () => { it('reject the promise', () => { const football = {}; const onReject = sinon.stub(); Promise.race({}).catch(() => football).then(onReject); tick(); sinon.assert.calledOnce(onReject); sinon.assert.calledWithExactly(onReject, sinon.match.same(football)); }); }); describe('argument is a generator', () => { it('resolves with the first resolved value', () => { function *gen() { yield new Promise(resolve => setTimeout(resolve, 100, 1)); yield new Promise(resolve => setTimeout(resolve, 200, 2)); } const onResolve = sinon.stub(); Promise.race(gen()).then(onResolve); tick(200); sinon.assert.calledOnce(onResolve); sinon.assert.calledWithExactly(onResolve, 1); }); it('resolves with the first non-promise value', () => { function *gen() { yield 1; yield new Promise(resolve => setTimeout(resolve, 200, 2)); } const onResolve = sinon.stub(); Promise.race(gen()).then(onResolve); tick(200); sinon.assert.calledOnce(onResolve); sinon.assert.calledWithExactly(onResolve, 1); }); it('iterates all values from the generator, even if one is already "resolved"', () => { let yieldCount = 0; function *gen() { yieldCount += 1; yield 1; yieldCount += 1; yield new Promise(resolve => setTimeout(resolve, 200, 2)); } const onResolve = sinon.stub(); Promise.race(gen()).then(onResolve); tick(200); sinon.assert.calledOnce(onResolve); sinon.assert.calledWithExactly(onResolve, 1); expect(yieldCount).to.be(2); }); }); }); });
describe('getSeries', function () { let getSeries; let agg = { fieldFormatter: _.constant(_.identity) }; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { getSeries = Private(AggResponsePointSeriesGetSeriesProvider); })); function wrapRows(row) { return row.map(function (v) { return { value: v }; }); } it('produces a single series with points for each row', function () { let rows = [ [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3] ].map(wrapRows); let chart = { aspects: { x: { i: 0 }, y: { i: 1 }, z: { i: 2 } } }; let series = getSeries(rows, chart); expect(series) .to.be.an('array') .and.to.have.length(1); let siri = series[0]; expect(siri) .to.be.an('object') .and.have.property('label', '') .and.have.property('values'); expect(siri.values) .to.be.an('array') .and.have.length(5); siri.values.forEach(function (point) { expect(point) .to.have.property('x', 1) .and.property('y', 2) .and.property('z', 3); }); }); it('produces multiple series if there are multiple y aspects', function () { let rows = [ [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3] ].map(wrapRows); let chart = { aspects: { x: { i: 0 }, y: [ { i: 1, col: { title: '0' }, agg: { id: 1 } }, { i: 2, col: { title: '1' }, agg: { id: 2 } }, ] } }; let series = getSeries(rows, chart); expect(series) .to.be.an('array') .and.to.have.length(2); series.forEach(function (siri, i) { expect(siri) .to.be.an('object') .and.have.property('label', '' + i) .and.have.property('values'); expect(siri.values) .to.be.an('array') .and.have.length(5); siri.values.forEach(function (point) { expect(point) .to.have.property('x', 1) .and.property('y', i + 2); }); }); }); it('produces multiple series if there is a series aspect', function () { let rows = [ ['0', 3], ['1', 3], ['1', 'NaN'], ['0', 3], ['0', 'NaN'], ['1', 3], ['0', 3], ['1', 3] ].map(wrapRows); let chart = { aspects: { x: { i: -1 }, series: { i: 0, agg: agg }, y: { i: 1, col: { title: '0' } } } }; let series = getSeries(rows, chart); expect(series) .to.be.an('array') .and.to.have.length(2); series.forEach(function (siri, i) { expect(siri) .to.be.an('object') .and.have.property('label', '' + i) .and.have.property('values'); expect(siri.values) .to.be.an('array') .and.have.length(3); siri.values.forEach(function (point) { expect(point) .to.have.property('x', '_all') .and.property('y', 3); }); }); }); it('produces multiple series if there is a series aspect and multipl y aspects', function () { let rows = [ ['0', 3, 4], ['1', 3, 4], ['0', 3, 4], ['1', 3, 4], ['0', 3, 4], ['1', 3, 4] ].map(wrapRows); let chart = { aspects: { x: { i: -1 }, series: { i: 0, agg: agg }, y: [ { i: 1, col: { title: '0' }, agg: { id: 1 } }, { i: 2, col: { title: '1' }, agg: { id: 2 } } ] } }; let series = getSeries(rows, chart); expect(series) .to.be.an('array') .and.to.have.length(4); // two series * two metrics checkSiri(series[0], '0: 0', 3); checkSiri(series[1], '0: 1', 4); checkSiri(series[2], '1: 0', 3); checkSiri(series[3], '1: 1', 4); function checkSiri(siri, label, y) { expect(siri) .to.be.an('object') .and.have.property('label', label) .and.have.property('values'); expect(siri.values) .to.be.an('array') .and.have.length(3); siri.values.forEach(function (point) { expect(point) .to.have.property('x', '_all') .and.property('y', y); }); } }); it('produces a series list in the same order as its corresponding metric column', function () { let rows = [ ['0', 3, 4], ['1', 3, 4], ['0', 3, 4], ['1', 3, 4], ['0', 3, 4], ['1', 3, 4] ].map(wrapRows); let chart = { aspects: { x: { i: -1 }, series: { i: 0, agg: agg }, y: [ { i: 1, col: { title: '0' }, agg: { id: 1 } }, { i: 2, col: { title: '1' }, agg: { id: 2 } } ] } }; let series = getSeries(rows, chart); expect(series[0]).to.have.property('label', '0: 0'); expect(series[1]).to.have.property('label', '0: 1'); expect(series[2]).to.have.property('label', '1: 0'); expect(series[3]).to.have.property('label', '1: 1'); // switch the order of the y columns chart.aspects.y = chart.aspects.y.reverse(); chart.aspects.y.forEach(function (y, i) { y.i = i; }); let series2 = getSeries(rows, chart); expect(series2[0]).to.have.property('label', '0: 1'); expect(series2[1]).to.have.property('label', '0: 0'); expect(series2[2]).to.have.property('label', '1: 1'); expect(series2[3]).to.have.property('label', '1: 0'); }); });
describe('Vislib xAxis Class Test Suite', function () { let XAxis; let Data; let persistedState; let xAxis; let el; let fixture; let dataObj; let data = { hits: 621, label: '', ordered: { date: true, interval: 30000, max: 1408734982458, min: 1408734082458 }, series: [ { values: [ { x: 1408734060000, y: 8 }, { x: 1408734090000, y: 23 }, { x: 1408734120000, y: 30 }, { x: 1408734150000, y: 28 }, { x: 1408734180000, y: 36 }, { x: 1408734210000, y: 30 }, { x: 1408734240000, y: 26 }, { x: 1408734270000, y: 22 }, { x: 1408734300000, y: 29 }, { x: 1408734330000, y: 24 } ] } ], xAxisFormatter: function (thing) { return new Date(thing); }, xAxisLabel: 'Date Histogram', yAxisLabel: 'Count' }; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { Data = Private(VislibLibDataProvider); persistedState = new (Private(PersistedStatePersistedStateProvider))(); XAxis = Private(VislibLibXAxisProvider); el = d3.select('body').append('div') .attr('class', 'x-axis-wrapper') .style('height', '40px'); fixture = el.append('div') .attr('class', 'x-axis-div'); dataObj = new Data(data, {}, persistedState); xAxis = new XAxis({ el: $('.x-axis-div')[0], xValues: dataObj.xValues(), ordered: dataObj.get('ordered'), xAxisFormatter: dataObj.get('xAxisFormatter'), _attr: { margin: { top: 0, right: 0, bottom: 0, left: 0 } } }); })); afterEach(function () { fixture.remove(); el.remove(); }); describe('render Method', function () { beforeEach(function () { xAxis.render(); }); it('should append an svg to div', function () { expect(el.selectAll('svg').length).to.be(1); }); it('should append a g element to the svg', function () { expect(el.selectAll('svg').select('g').length).to.be(1); }); it('should append ticks with text', function () { expect(!!el.selectAll('svg').selectAll('.tick text')).to.be(true); }); }); describe('getScale, getDomain, getTimeDomain, getOrdinalDomain, and getRange Methods', function () { let ordered; let timeScale; let timeDomain; let ordinalScale; let ordinalDomain; let width; let range; beforeEach(function () { timeScale = xAxis.getScale(); timeDomain = xAxis.getDomain(timeScale); range = xAxis.getRange(timeDomain, width); xAxis.ordered = {}; ordinalScale = xAxis.getScale(); ordinalDomain = ordinalScale.domain(['this', 'should', 'be', 'an', 'array']); width = $('.x-axis-div').width(); }); it('should return a function', function () { expect(_.isFunction(timeScale)).to.be(true); expect(_.isFunction(ordinalScale)).to.be(true); }); it('should return the correct domain', function () { expect(_.isDate(timeDomain.domain()[0])).to.be(true); expect(_.isDate(timeDomain.domain()[1])).to.be(true); }); it('should return the min and max dates', function () { expect(timeDomain.domain()[0].toDateString()).to.be(new Date(1408734060000).toDateString()); expect(timeDomain.domain()[1].toDateString()).to.be(new Date(1408734330000).toDateString()); }); it('should return an ordinal scale', function () { expect(ordinalDomain.domain()[0]).to.be('this'); expect(ordinalDomain.domain()[4]).to.be('array'); }); it('should return an array of values', function () { expect(_.isArray(ordinalDomain.domain())).to.be(true); }); it('should return the correct range', function () { expect(range.range()[0]).to.be(0); expect(range.range()[1]).to.be(width); }); }); describe('getXScale Method', function () { let width; let xScale; beforeEach(function () { width = $('.x-axis-div').width(); xScale = xAxis.getXScale(width); }); it('should return a function', function () { expect(_.isFunction(xScale)).to.be(true); }); it('should return a domain', function () { expect(_.isDate(xScale.domain()[0])).to.be(true); expect(_.isDate(xScale.domain()[1])).to.be(true); }); it('should return a range', function () { expect(xScale.range()[0]).to.be(0); expect(xScale.range()[1]).to.be(width); }); }); describe('getXAxis Method', function () { let width; let axis; beforeEach(function () { width = $('.x-axis-div').width(); xAxis.getXAxis(width); }); it('should create an xAxis function on the xAxis class', function () { expect(_.isFunction(xAxis.xAxis)).to.be(true); }); it('should create an xScale function on the xAxis class', function () { expect(_.isFunction(xAxis.xScale)).to.be(true); }); it('should create an xAxisFormatter function on the xAxis class', function () { expect(_.isFunction(xAxis.xAxisFormatter)).to.be(true); }); }); describe('draw Method', function () { it('should be a function', function () { expect(_.isFunction(xAxis.draw())).to.be(true); }); }); });
describe('get filters', function () { const storeNames = { app: 'appState', global: 'globalState' }; let queryFilter; let appState; let globalState; beforeEach(ngMock.module( 'kibana', 'kibana/global_state', function ($provide) { appState = new MockState({ filters: [] }); $provide.service('getAppState', function () { return function () { return appState; }; }); globalState = new MockState({ filters: [] }); $provide.service('globalState', function () { return globalState; }); } )); beforeEach(ngMock.inject(function (_$rootScope_, Private) { queryFilter = Private(FilterBarQueryFilterProvider); })); describe('getFilters method', function () { let filters; beforeEach(function () { filters = [ { query: { match: { extension: { query: 'jpg', type: 'phrase' } } } }, { query: { match: { '@tags': { query: 'info', type: 'phrase' } } } }, null ]; }); it('should return app and global filters', function () { appState.filters = [filters[0]]; globalState.filters = [filters[1]]; // global filters should be listed first let res = queryFilter.getFilters(); expect(res[0]).to.eql(filters[1]); expect(res[1]).to.eql(filters[0]); // should return updated version of filters const newFilter = { query: { match: { '_type': { query: 'nginx', type: 'phrase' } } } }; appState.filters.push(newFilter); res = queryFilter.getFilters(); expect(res).to.contain(newFilter); }); it('should append the state store', function () { appState.filters = [filters[0]]; globalState.filters = [filters[1]]; const res = queryFilter.getFilters(); expect(res[0].$state.store).to.be(storeNames.global); expect(res[1].$state.store).to.be(storeNames.app); }); it('should return non-null filters from specific states', function () { const states = [ [ globalState, queryFilter.getGlobalFilters ], [ appState, queryFilter.getAppFilters ], ]; _.each(states, function (state) { state[0].filters = filters.slice(0); expect(state[0].filters).to.contain(null); const res = state[1](); expect(res.length).to.be(state[0].filters.length); expect(state[0].filters).to.not.contain(null); }); }); it('should replace the state, not save it', function () { const states = [ [ globalState, queryFilter.getGlobalFilters ], [ appState, queryFilter.getAppFilters ], ]; expect(appState.save.called).to.be(false); expect(appState.replace.called).to.be(false); _.each(states, function (state) { expect(state[0].save.called).to.be(false); expect(state[0].replace.called).to.be(false); state[0].filters = filters.slice(0); state[1](); expect(state[0].save.called).to.be(false); expect(state[0].replace.called).to.be(true); }); }); }); describe('filter reconciliation', function () { let filters; beforeEach(function () { filters = [ { query: { match: { extension: { query: 'jpg', type: 'phrase' } } }, meta: { negate: false, disabled: false } }, { query: { match: { '@tags': { query: 'info', type: 'phrase' } } }, meta: { negate: false, disabled: false } }, { query: { match: { '_type': { query: 'nginx', type: 'phrase' } } }, meta: { negate: false, disabled: false } } ]; }); it('should skip appState filters that match globalState filters', function () { globalState.filters = filters; const appFilter = _.cloneDeep(filters[1]); appState.filters.push(appFilter); // global filters should be listed first const res = queryFilter.getFilters(); expect(res).to.have.length(3); _.each(res, function (filter) { expect(filter.$state.store).to.be('globalState'); }); }); it('should append conflicting appState filters', function () { globalState.filters = filters; const appFilter = _.cloneDeep(filters[1]); appFilter.meta.negate = true; appState.filters.push(appFilter); // global filters should be listed first const res = queryFilter.getFilters(); expect(res).to.have.length(4); expect(res.filter(function (filter) { return filter.$state.store === storeNames.global; }).length).to.be(3); expect(res.filter(function (filter) { return filter.$state.store === storeNames.app; }).length).to.be(1); }); it('should not affect disabled filters', function () { // test adding to globalState globalState.filters = _.map(filters, function (filter) { const f = _.cloneDeep(filter); f.meta.disabled = true; return f; }); _.each(filters, function (filter) { globalState.filters.push(filter); }); let res = queryFilter.getFilters(); expect(res).to.have.length(6); // test adding to appState globalState.filters = _.map(filters, function (filter) { const f = _.cloneDeep(filter); f.meta.disabled = true; return f; }); _.each(filters, function (filter) { appState.filters.push(filter); }); res = queryFilter.getFilters(); expect(res).to.have.length(6); }); }); });
describe('function fetchAnchor', function () { let fetchAnchor; let SearchSourceStub; beforeEach(ngMock.module(function createServiceStubs($provide) { $provide.value('courier', createCourierStub()); })); beforeEach(ngMock.inject(function createPrivateStubs(Private) { SearchSourceStub = createSearchSourceStubProvider([ { _id: 'hit1' }, ]); Private.stub(SearchSourceProvider, SearchSourceStub); fetchAnchor = Private(fetchAnchorProvider); })); it('should use the `fetchAsRejectablePromise` method of the SearchSource', function () { const searchSourceStub = new SearchSourceStub(); return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }]) .then(() => { expect(searchSourceStub.fetchAsRejectablePromise.calledOnce).to.be(true); }); }); it('should configure the SearchSource to not inherit from the implicit root', function () { const searchSourceStub = new SearchSourceStub(); return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }]) .then(() => { const inheritsSpy = searchSourceStub.inherits; expect(inheritsSpy.calledOnce).to.be(true); expect(inheritsSpy.firstCall.args[0]).to.eql(false); }); }); it('should set the SearchSource index pattern', function () { const searchSourceStub = new SearchSourceStub(); return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }]) .then(() => { const setIndexSpy = searchSourceStub.set.withArgs('index'); expect(setIndexSpy.calledOnce).to.be(true); expect(setIndexSpy.firstCall.args[1]).to.eql({ id: 'INDEX_PATTERN_ID' }); }); }); it('should set the SearchSource version flag to true', function () { const searchSourceStub = new SearchSourceStub(); return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }]) .then(() => { const setVersionSpy = searchSourceStub.set.withArgs('version'); expect(setVersionSpy.calledOnce).to.be(true); expect(setVersionSpy.firstCall.args[1]).to.eql(true); }); }); it('should set the SearchSource size to 1', function () { const searchSourceStub = new SearchSourceStub(); return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }]) .then(() => { const setSizeSpy = searchSourceStub.set.withArgs('size'); expect(setSizeSpy.calledOnce).to.be(true); expect(setSizeSpy.firstCall.args[1]).to.eql(1); }); }); it('should set the SearchSource query to a _uid terms query', function () { const searchSourceStub = new SearchSourceStub(); return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }]) .then(() => { const setQuerySpy = searchSourceStub.set.withArgs('query'); expect(setQuerySpy.calledOnce).to.be(true); expect(setQuerySpy.firstCall.args[1]).to.eql({ query: { terms: { _uid: ['UID'], } }, language: 'lucene' }); }); }); it('should set the SearchSource sort order', function () { const searchSourceStub = new SearchSourceStub(); return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }]) .then(() => { const setSortSpy = searchSourceStub.set.withArgs('sort'); expect(setSortSpy.calledOnce).to.be(true); expect(setSortSpy.firstCall.args[1]).to.eql([ { '@timestamp': 'desc' }, { '_doc': 'asc' }, ]); }); }); it('should reject with an error when no hits were found', function () { const searchSourceStub = new SearchSourceStub(); searchSourceStub._stubHits = []; return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }]) .then( () => { expect().fail('expected the promise to be rejected'); }, (error) => { expect(error).to.be.an(Error); } ); }); it('should return the first hit after adding an anchor marker', function () { const searchSourceStub = new SearchSourceStub(); searchSourceStub._stubHits = [ { property1: 'value1' }, { property2: 'value2' }, ]; return fetchAnchor('INDEX_PATTERN_ID', 'UID', [{ '@timestamp': 'desc' }, { '_doc': 'asc' }]) .then((anchorDocument) => { expect(anchorDocument).to.have.property('property1', 'value1'); expect(anchorDocument).to.have.property('$$_isAnchor', true); }); }); });
describe('visualize directive', function () { let $rootScope; let $compile; let $scope; let $el; let Vis; let indexPattern; let fixtures; let searchSource; let appState; beforeEach(ngMock.module('kibana', 'kibana/table_vis')); beforeEach(ngMock.inject(function (Private, $injector) { $rootScope = $injector.get('$rootScope'); $compile = $injector.get('$compile'); fixtures = require('fixtures/fake_hierarchical_data'); Vis = Private(VisProvider); appState = new MockState({ filters: [] }); appState.toJSON = () => { return {}; }; indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); searchSource = Private(FixturesStubbedSearchSourceProvider); const requiresSearch = false; init(new CreateVis(null, requiresSearch), fixtures.oneRangeBucket); })); afterEach(() => { $scope.$destroy(); }); // basically a parameterized beforeEach function init(vis, esResponse) { vis.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); }); $rootScope.vis = vis; $rootScope.esResponse = esResponse; $rootScope.uiState = require('fixtures/mock_ui_state'); $rootScope.appState = appState; $rootScope.appState.vis = vis.getState(); $rootScope.searchSource = searchSource; $rootScope.savedObject = { vis: vis, searchSource: searchSource }; $el = $('<visualize saved-obj="savedObject" ui-state="uiState" app-state="appState">'); $compile($el)($rootScope); $rootScope.$apply(); $scope = $el.isolateScope(); } function CreateVis(params, requiresSearch) { const vis = new Vis(indexPattern, { type: 'table', params: params || {}, aggs: [ { type: 'count', schema: 'metric' }, { type: 'range', schema: 'bucket', params: { field: 'bytes', ranges: [ { from: 0, to: 1000 }, { from: 1000, to: 2000 } ] } } ] }); vis.type.requestHandler = requiresSearch ? 'default' : 'none'; vis.type.responseHandler = 'none'; vis.type.requiresSearch = false; return vis; } it('searchSource.onResults should not be called when requiresSearch is false', function () { searchSource.crankResults(); $scope.$digest(); expect(searchSource.getOnResultsCount()).to.be(0); }); it('fetches new data on update event', () => { let counter = 0; $scope.fetch = () => { counter++; }; $scope.vis.emit('update'); expect(counter).to.equal(1); }); it('updates the appState in editor mode on update event', () => { $scope.editorMode = true; $scope.appState.vis = {}; $scope.vis.emit('update'); expect($scope.appState.vis).to.not.equal({}); }); it('sets force flag on force event', () => { $scope.vis.emit('reload'); expect($scope.vis.reload).to.equal(true); }); });
describe('IndexPattern#flattenHit()', function () { let flattenHit; let config; let hit; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private, $injector) { const indexPattern = { fields: { byName: { 'tags.text': { type: 'string' }, 'tags.label': { type: 'string' }, 'message': { type: 'string' }, 'geo.coordinates': { type: 'geo_point' }, 'geo.dest': { type: 'string' }, 'geo.src': { type: 'string' }, 'bytes': { type: 'number' }, '@timestamp': { type: 'date' }, 'team': { type: 'nested' }, 'team.name': { type: 'string' }, 'team.role': { type: 'string' }, 'user': { type: 'conflict' }, 'user.name': { type: 'string' }, 'user.id': { type: 'conflict' }, 'delta': { type: 'number', scripted: true } } } }; const cachedFlatten = Private(IndexPatternsFlattenHitProvider)(indexPattern); flattenHit = function (hit, deep = false) { delete hit.$$_flattened; return cachedFlatten(hit, deep); }; config = $injector.get('config'); hit = { _source: { message: 'Hello World', geo: { coordinates: { lat: 33.4500, lon: 112.0667 }, dest: 'US', src: 'IN' }, bytes: 10039103, '@timestamp': (new Date()).toString(), tags: [ { text: 'foo', label: [ 'FOO1', 'FOO2' ] }, { text: 'bar', label: 'BAR' } ], groups: ['loners'], noMapping: true, team: [ { name: 'foo', role: 'leader' }, { name: 'bar', role: 'follower' }, { name: 'baz', role: 'party boy' }, ], user: { name: 'smith', id: 123 } }, fields: { delta: [42], random: [0.12345] } }; })); it('flattens keys as far down as the mapping goes', function () { const flat = flattenHit(hit); expect(flat).to.have.property('geo.coordinates', hit._source.geo.coordinates); expect(flat).to.not.have.property('geo.coordinates.lat'); expect(flat).to.not.have.property('geo.coordinates.lon'); expect(flat).to.have.property('geo.dest', 'US'); expect(flat).to.have.property('geo.src', 'IN'); expect(flat).to.have.property('@timestamp', hit._source['@timestamp']); expect(flat).to.have.property('message', 'Hello World'); expect(flat).to.have.property('bytes', 10039103); }); it('flattens keys not in the mapping', function () { const flat = flattenHit(hit); expect(flat).to.have.property('noMapping', true); expect(flat).to.have.property('groups'); expect(flat.groups).to.eql(['loners']); }); it('flattens conflicting types in the mapping', function () { const flat = flattenHit(hit); expect(flat).to.not.have.property('user'); expect(flat).to.have.property('user.name', hit._source.user.name); expect(flat).to.have.property('user.id', hit._source.user.id); }); it('should preserve objects in arrays if deep argument is false', function () { const flat = flattenHit(hit); expect(flat).to.have.property('tags', hit._source.tags); }); it('should expand objects in arrays if deep argument is true', function () { const flat = flattenHit(hit, true); expect(flat['tags.text']).to.be.eql([ 'foo', 'bar' ]); }); it('should support arrays when expanding objects in arrays if deep argument is true', function () { const flat = flattenHit(hit, true); expect(flat['tags.label']).to.be.eql([ 'FOO1', 'FOO2', 'BAR' ]); }); it('does not enter into nested fields', function () { const flat = flattenHit(hit); expect(flat).to.have.property('team', hit._source.team); expect(flat).to.not.have.property('team.name'); expect(flat).to.not.have.property('team.role'); expect(flat).to.not.have.property('team[0]'); expect(flat).to.not.have.property('team.0'); }); it('unwraps script fields', function () { const flat = flattenHit(hit); expect(flat).to.have.property('delta', 42); }); it('assumes that all fields are "computed fields"', function () { const flat = flattenHit(hit); expect(flat).to.have.property('random', 0.12345); }); it('ignores fields that start with an _ and are not in the metaFields', function () { config.set('metaFields', ['_metaKey']); hit.fields._notMetaKey = [100]; const flat = flattenHit(hit); expect(flat).to.not.have.property('_notMetaKey'); }); it('includes underscore-prefixed keys that are in the metaFields', function () { config.set('metaFields', ['_metaKey']); hit.fields._metaKey = [100]; const flat = flattenHit(hit); expect(flat).to.have.property('_metaKey', 100); }); it('adapts to changes in the metaFields', function () { hit.fields._metaKey = [100]; config.set('metaFields', ['_metaKey']); let flat = flattenHit(hit); expect(flat).to.have.property('_metaKey', 100); config.set('metaFields', []); flat = flattenHit(hit); expect(flat).to.not.have.property('_metaKey'); }); it('handles fields that are not arrays, like _timestamp', function () { hit.fields._metaKey = 20000; config.set('metaFields', ['_metaKey']); const flat = flattenHit(hit); expect(flat).to.have.property('_metaKey', 20000); }); });
describe('Base Methods', function () { var MarkerClass; beforeEach(ngMock.module('MarkerFactory')); beforeEach(ngMock.inject(function (Private) { MarkerClass = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); markerLayer = createMarker(MarkerClass); })); describe('filterToMapBounds', function () { it('should not filter any features', function () { // set bounds to the entire world setBounds([-87.252, -343.828], [87.252, 343.125]); var boundFilter = markerLayer._filterToMapBounds(); var mapFeature = mapData.features.filter(boundFilter); expect(mapFeature.length).to.equal(mapData.features.length); }); it('should filter out data points that are outside of the map bounds', function () { // set bounds to roughly US southwest setBounds([31.690, -124.387], [42.324, -102.919]); var boundFilter = markerLayer._filterToMapBounds(); var mapFeature = mapData.features.filter(boundFilter); expect(mapFeature.length).to.be.lessThan(mapData.features.length); }); }); describe('legendQuantizer', function () { it('should return a range of hex colors', function () { var minColor = markerLayer._legendQuantizer(mapData.properties.allmin); var maxColor = markerLayer._legendQuantizer(mapData.properties.allmax); expect(minColor.substring(0, 1)).to.equal('#'); expect(minColor).to.have.length(7); expect(maxColor.substring(0, 1)).to.equal('#'); expect(maxColor).to.have.length(7); expect(minColor).to.not.eql(maxColor); }); it('should return a color with 1 color', function () { var geoJson = { properties: { min: 1, max: 1 } }; markerLayer = createMarker(MarkerClass, geoJson); // ensure the quantizer domain is correct var color = markerLayer._legendQuantizer(1); expect(color).to.not.be(undefined); expect(color.substring(0, 1)).to.equal('#'); // should always get the same color back _.times(5, function () { var num = _.random(0, 100); var randColor = markerLayer._legendQuantizer(0); expect(randColor).to.equal(color); }); }); }); describe('applyShadingStyle', function () { it('should return a style object', function () { var style = markerLayer.applyShadingStyle(100); expect(style).to.be.an('object'); var keys = _.keys(style); var expected = ['fillColor', 'color']; _.each(expected, function (key) { expect(keys).to.contain(key); }); }); it('should use the legendQuantizer', function () { var spy = sinon.spy(markerLayer, '_legendQuantizer'); var style = markerLayer.applyShadingStyle(100); expect(spy.callCount).to.equal(1); }); }); describe('showTooltip', function () { it('should use the tooltip formatter', function () { var content; var sample = _.sample(mapData.features); var stub = sinon.stub(markerLayer, '_tooltipFormatter', function (val) { return; }); markerLayer._showTooltip(sample); expect(stub.callCount).to.equal(1); expect(stub.firstCall.calledWith(sample)).to.be(true); }); }); describe('addLegend', function () { var addToSpy; var leafletControlStub; beforeEach(function () { addToSpy = sinon.spy(); leafletControlStub = sinon.stub(L, 'control', function (options) { return { addTo: addToSpy }; }); }); it('should do nothing if there is already a legend', function () { markerLayer._legend = { legend: 'exists' }; // anything truthy markerLayer.addLegend(); expect(leafletControlStub.callCount).to.equal(0); }); it('should create a leaflet control', function () { markerLayer.addLegend(); expect(leafletControlStub.callCount).to.equal(1); expect(addToSpy.callCount).to.equal(1); expect(addToSpy.firstCall.calledWith(markerLayer.map)).to.be(true); expect(markerLayer._legend).to.have.property('onAdd'); }); it('should use the value formatter', function () { var formatterSpy = sinon.spy(markerLayer, '_valueFormatter'); // called twice for every legend color defined var expectedCallCount = markerLayer._legendColors.length * 2; markerLayer.addLegend(); var legend = markerLayer._legend.onAdd(); expect(formatterSpy.callCount).to.equal(expectedCallCount); expect(legend).to.be.a(HTMLDivElement); }); }); });
describe('config component', function () { let config; let $scope; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function ($injector) { config = $injector.get('config'); $scope = $injector.get('$rootScope'); })); describe('#get', function () { it('gives access to config values', function () { expect(config.get('dateFormat')).to.be.a('string'); }); it('supports the default value overload', function () { // default values are consumed and returned atomically expect(config.get('obscureProperty', 'default')).to.be('default'); // default values are consumed only if setting was previously unset expect(config.get('obscureProperty', 'another')).to.be('default'); // default values are persisted expect(config.get('obscureProperty')).to.be('default'); }); it('throws on unknown properties that don\'t have a value yet.', function () { const msg = 'Unexpected `config.get("throwableProperty")` call on unrecognized configuration setting'; expect(config.get).withArgs('throwableProperty').to.throwException(msg); }); }); describe('#set', function () { it('stores a value in the config val set', function () { const original = config.get('dateFormat'); config.set('dateFormat', 'notaformat'); expect(config.get('dateFormat')).to.be('notaformat'); config.set('dateFormat', original); }); it('stores a value in a previously unknown config key', function () { expect(config.set).withArgs('unrecognizedProperty', 'somevalue').to.not.throwException(); expect(config.get('unrecognizedProperty')).to.be('somevalue'); }); }); describe('#$bind', function () { it('binds a config key to a $scope property', function () { const dateFormat = config.get('dateFormat'); config.bindToScope($scope, 'dateFormat'); expect($scope).to.have.property('dateFormat', dateFormat); }); it('alows overriding the property name', function () { const dateFormat = config.get('dateFormat'); config.bindToScope($scope, 'dateFormat', 'defaultDateFormat'); expect($scope).to.not.have.property('dateFormat'); expect($scope).to.have.property('defaultDateFormat', dateFormat); }); it('keeps the property up to date', function () { const original = config.get('dateFormat'); const newDateFormat = original + ' NEW NEW NEW!'; config.bindToScope($scope, 'dateFormat'); expect($scope).to.have.property('dateFormat', original); config.set('dateFormat', newDateFormat); expect($scope).to.have.property('dateFormat', newDateFormat); config.set('dateFormat', original); }); }); });
const init = function (index, type, id) { ngMock.module('kibana'); // Stub services ngMock.module(function ($provide) { $provide.service('$route', function (Private) { this.current = { locals: { indexPattern: Private(FixturesStubbedLogstashIndexPatternProvider) }, params: { index: index || 'myIndex', type: type || 'myType', id: id || 'myId' } }; }); $provide.service('es', function (Private, $q) { this.search = function (config) { const deferred = $q.defer(); switch (config.index) { case 'goodSearch': deferred.resolve({ hits: { total: 1, hits: [{ _source: { foo: true } }] } }); break; case 'badSearch': deferred.resolve({ hits: { total: 0, hits: [] } }); break; case 'missingIndex': deferred.reject({status: 404}); break; case 'badRequest': deferred.reject({status: 500}); break; } return deferred.promise; }; }); }); // Create the scope ngMock.inject(function ($rootScope, $controller, _timefilter_) { $scope = $rootScope.$new(); timefilter = _timefilter_; createController = function () { return $controller('doc', { '$scope': $scope }); }; }); createController(); };
describe('index pattern', function () { NoDigestPromises.activateForSuite(); let IndexPattern; let mapper; let mappingSetup; let mockLogstashFields; let DocSource; let docSourceResponse; const indexPatternId = 'test-pattern'; let indexPattern; let calculateIndices; let intervals; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { mockLogstashFields = Private(FixturesLogstashFieldsProvider); docSourceResponse = Private(FixturesStubbedDocSourceResponseProvider); DocSource = Private(AdminDocSourceProvider); sinon.stub(DocSource.prototype, 'doIndex'); sinon.stub(DocSource.prototype, 'fetch'); mapper = stubMapper(Private, mockLogstashFields); // stub mappingSetup mappingSetup = Private(UtilsMappingSetupProvider); sinon.stub(mappingSetup, 'isDefined', function () { return Promise.resolve(true); }); // stub calculateIndices calculateIndices = sinon.spy(function () { return Promise.resolve([ { index: 'foo', max: Infinity, min: -Infinity }, { index: 'bar', max: Infinity, min: -Infinity } ]); }); Private.stub(IndexPatternsCalculateIndicesProvider, calculateIndices); // spy on intervals intervals = Private(IndexPatternsIntervalsProvider); sinon.stub(intervals, 'toIndexList').returns([ { index: 'foo', max: Infinity, min: -Infinity }, { index: 'bar', max: Infinity, min: -Infinity } ]); IndexPattern = Private(IndexPatternProvider); })); // create an indexPattern instance for each test beforeEach(function () { return create(indexPatternId).then(function (pattern) { indexPattern = pattern; }); }); // helper function to create index patterns function create(id, payload) { const indexPattern = new IndexPattern(id); DocSource.prototype.doIndex.returns(Promise.resolve(id)); payload = _.defaults(payload || {}, docSourceResponse(id)); setDocsourcePayload(payload); return indexPattern.init(); } function setDocsourcePayload(payload) { DocSource.prototype.fetch.returns(Promise.resolve(payload)); } describe('api', function () { it('should have expected properties', function () { return create('test-pattern').then(function (indexPattern) { // methods expect(indexPattern).to.have.property('refreshFields'); expect(indexPattern).to.have.property('popularizeField'); expect(indexPattern).to.have.property('getScriptedFields'); expect(indexPattern).to.have.property('getNonScriptedFields'); expect(indexPattern).to.have.property('getInterval'); expect(indexPattern).to.have.property('addScriptedField'); expect(indexPattern).to.have.property('removeScriptedField'); expect(indexPattern).to.have.property('toString'); expect(indexPattern).to.have.property('toJSON'); expect(indexPattern).to.have.property('save'); // properties expect(indexPattern).to.have.property('fields'); }); }); }); describe('init', function () { it('should append the found fields', function () { expect(DocSource.prototype.fetch.callCount).to.be(1); expect(indexPattern.fields).to.have.length(mockLogstashFields.length); expect(indexPattern.fields).to.be.an(IndexedArray); }); }); describe('fields', function () { it('should have expected properties on fields', function () { expect(indexPattern.fields[0]).to.have.property('displayName'); expect(indexPattern.fields[0]).to.have.property('filterable'); expect(indexPattern.fields[0]).to.have.property('format'); expect(indexPattern.fields[0]).to.have.property('sortable'); expect(indexPattern.fields[0]).to.have.property('scripted'); }); }); describe('getScriptedFields', function () { it('should return all scripted fields', function () { const scriptedNames = _(mockLogstashFields).where({ scripted: true }).pluck('name').value(); const respNames = _.pluck(indexPattern.getScriptedFields(), 'name'); expect(respNames).to.eql(scriptedNames); }); }); describe('getNonScriptedFields', function () { it('should return all non-scripted fields', function () { const notScriptedNames = _(mockLogstashFields).where({ scripted: false }).pluck('name').value(); const respNames = _.pluck(indexPattern.getNonScriptedFields(), 'name'); expect(respNames).to.eql(notScriptedNames); }); }); describe('refresh fields', function () { // override the default indexPattern, with a truncated field list const indexPatternId = 'test-pattern'; let indexPattern; let customFields; beforeEach(function () { customFields = [{ analyzed: true, count: 30, filterable: true, indexed: true, name: 'foo', scripted: false, sortable: true, type: 'number', aggregatable: true, searchable: false }, { name: 'script number', type: 'number', scripted: true, script: '1234', lang: 'expression' }]; return create(indexPatternId, { _source: { customFormats: '{}', fields: JSON.stringify(customFields) } }).then(function (pattern) { indexPattern = pattern; }); }); it('should fetch fields from the doc source', function () { // ensure that we don't have all the fields expect(customFields.length).to.not.equal(mockLogstashFields.length); expect(indexPattern.fields).to.have.length(customFields.length); // ensure that all fields will be included in the returned docSource setDocsourcePayload(docSourceResponse(indexPatternId)); return Promise.all([ // read fields from elasticsearch mapper.getFieldsForIndexPattern(), // tell the index pattern to do the same indexPattern.refreshFields(), ]) .then(function (data) { const expected = data[0]; // just the fields in the index const fields = indexPattern.getNonScriptedFields(); // get all but scripted fields expect(_.pluck(fields, 'name')).to.eql(_.pluck(expected, 'name')); }); }); it('should preserve the scripted fields', function () { // ensure that all fields will be included in the returned docSource setDocsourcePayload(docSourceResponse(indexPatternId)); // add spy to indexPattern.getScriptedFields const scriptedFieldsSpy = sinon.spy(indexPattern, 'getScriptedFields'); // refresh fields, which will fetch return indexPattern.refreshFields().then(function () { // called to append scripted fields to the response from mapper.getFieldsForIndexPattern expect(scriptedFieldsSpy.callCount).to.equal(1); const expected = _.filter(indexPattern.fields, { scripted: true }); expect(_.pluck(expected, 'name')).to.eql(['script number']); }); }); }); describe('add and remove scripted fields', function () { it('should append the scripted field', function () { // keep a copy of the current scripted field count const saveSpy = sinon.spy(indexPattern, 'save'); const oldCount = indexPattern.getScriptedFields().length; // add a new scripted field const scriptedField = { name: 'new scripted field', script: 'false', type: 'boolean' }; indexPattern.addScriptedField(scriptedField.name, scriptedField.script, scriptedField.type); const scriptedFields = indexPattern.getScriptedFields(); expect(saveSpy.callCount).to.equal(1); expect(scriptedFields).to.have.length(oldCount + 1); expect(indexPattern.fields.byName[scriptedField.name].name).to.equal(scriptedField.name); }); it('should remove scripted field, by name', function () { const saveSpy = sinon.spy(indexPattern, 'save'); const scriptedFields = indexPattern.getScriptedFields(); const oldCount = scriptedFields.length; const scriptedField = _.last(scriptedFields); indexPattern.removeScriptedField(scriptedField.name); expect(saveSpy.callCount).to.equal(1); expect(indexPattern.getScriptedFields().length).to.equal(oldCount - 1); expect(indexPattern.fields.byName[scriptedField.name]).to.equal(undefined); }); it('should not allow duplicate names', function () { const scriptedFields = indexPattern.getScriptedFields(); const scriptedField = _.last(scriptedFields); expect(function () { indexPattern.addScriptedField(scriptedField.name, '\'new script\'', 'string'); }).to.throwError(function (e) { expect(e).to.be.a(DuplicateField); }); }); }); describe('popularizeField', function () { it('should increment the poplarity count by default', function () { const saveSpy = sinon.stub(indexPattern, 'save'); indexPattern.fields.forEach(function (field, i) { const oldCount = field.count; indexPattern.popularizeField(field.name); expect(saveSpy.callCount).to.equal(i + 1); expect(field.count).to.equal(oldCount + 1); }); }); it('should increment the poplarity count', function () { const saveSpy = sinon.stub(indexPattern, 'save'); indexPattern.fields.forEach(function (field, i) { const oldCount = field.count; const incrementAmount = 4; indexPattern.popularizeField(field.name, incrementAmount); expect(saveSpy.callCount).to.equal(i + 1); expect(field.count).to.equal(oldCount + incrementAmount); }); }); it('should decrement the poplarity count', function () { indexPattern.fields.forEach(function (field) { const oldCount = field.count; const incrementAmount = 4; const decrementAmount = -2; indexPattern.popularizeField(field.name, incrementAmount); indexPattern.popularizeField(field.name, decrementAmount); expect(field.count).to.equal(oldCount + incrementAmount + decrementAmount); }); }); it('should not go below 0', function () { indexPattern.fields.forEach(function (field) { const decrementAmount = -Number.MAX_VALUE; indexPattern.popularizeField(field.name, decrementAmount); expect(field.count).to.equal(0); }); }); }); describe('#toDetailedIndexList', function () { describe('when index pattern is an interval', function () { let interval; beforeEach(function () { interval = 'result:getInterval'; sinon.stub(indexPattern, 'getInterval').returns(interval); }); it('invokes interval toDetailedIndexList with given start/stop times', async function () { await indexPattern.toDetailedIndexList(1, 2); const id = indexPattern.id; expect(intervals.toIndexList.calledWith(id, interval, 1, 2)).to.be(true); }); it('is fulfilled by the result of interval toDetailedIndexList', async function () { const indexList = await indexPattern.toDetailedIndexList(); expect(indexList[0].index).to.equal('foo'); expect(indexList[1].index).to.equal('bar'); }); describe('with sort order', function () { it('passes the sort order to the intervals module', function () { return indexPattern.toDetailedIndexList(1, 2, 'SORT_DIRECTION') .then(function () { expect(intervals.toIndexList.callCount).to.be(1); expect(intervals.toIndexList.getCall(0).args[4]).to.be('SORT_DIRECTION'); }); }); }); }); describe('when index pattern is a time-base wildcard', function () { beforeEach(function () { sinon.stub(indexPattern, 'getInterval').returns(false); sinon.stub(indexPattern, 'hasTimeField').returns(true); sinon.stub(indexPattern, 'isWildcard').returns(true); }); it('invokes calculateIndices with given start/stop times and sortOrder', async function () { await indexPattern.toDetailedIndexList(1, 2, 'sortOrder'); const id = indexPattern.id; const field = indexPattern.timeFieldName; expect(calculateIndices.calledWith(id, field, 1, 2, 'sortOrder')).to.be(true); }); it('is fulfilled by the result of calculateIndices', async function () { const indexList = await indexPattern.toDetailedIndexList(); expect(indexList[0].index).to.equal('foo'); expect(indexList[1].index).to.equal('bar'); }); }); describe('when index pattern is a time-base wildcard that is configured not to expand', function () { beforeEach(function () { sinon.stub(indexPattern, 'getInterval').returns(false); sinon.stub(indexPattern, 'hasTimeField').returns(true); sinon.stub(indexPattern, 'isWildcard').returns(true); sinon.stub(indexPattern, 'canExpandIndices').returns(false); }); it('is fulfilled by id', async function () { const indexList = await indexPattern.toDetailedIndexList(); expect(indexList.index).to.equal(indexPattern.id); }); }); describe('when index pattern is neither an interval nor a time-based wildcard', function () { beforeEach(function () { sinon.stub(indexPattern, 'getInterval').returns(false); }); it('is fulfilled by id', async function () { const indexList = await indexPattern.toDetailedIndexList(); expect(indexList.index).to.equal(indexPattern.id); }); }); }); describe('#toIndexList', function () { describe('when index pattern is an interval', function () { let interval; beforeEach(function () { interval = 'result:getInterval'; sinon.stub(indexPattern, 'getInterval').returns(interval); }); it('invokes interval toIndexList with given start/stop times', async function () { await indexPattern.toIndexList(1, 2); const id = indexPattern.id; expect(intervals.toIndexList.calledWith(id, interval, 1, 2)).to.be(true); }); it('is fulfilled by the result of interval toIndexList', async function () { const indexList = await indexPattern.toIndexList(); expect(indexList[0]).to.equal('foo'); expect(indexList[1]).to.equal('bar'); }); describe('with sort order', function () { it('passes the sort order to the intervals module', function () { return indexPattern.toIndexList(1, 2, 'SORT_DIRECTION') .then(function () { expect(intervals.toIndexList.callCount).to.be(1); expect(intervals.toIndexList.getCall(0).args[4]).to.be('SORT_DIRECTION'); }); }); }); }); describe('when index pattern is a time-base wildcard', function () { beforeEach(function () { sinon.stub(indexPattern, 'getInterval').returns(false); sinon.stub(indexPattern, 'hasTimeField').returns(true); sinon.stub(indexPattern, 'isWildcard').returns(true); }); it('invokes calculateIndices with given start/stop times and sortOrder', async function () { await indexPattern.toIndexList(1, 2, 'sortOrder'); const id = indexPattern.id; const field = indexPattern.timeFieldName; expect(calculateIndices.calledWith(id, field, 1, 2, 'sortOrder')).to.be(true); }); it('is fulfilled by the result of calculateIndices', async function () { const indexList = await indexPattern.toIndexList(); expect(indexList[0]).to.equal('foo'); expect(indexList[1]).to.equal('bar'); }); }); describe('when index pattern is a time-base wildcard that is configured not to expand', function () { beforeEach(function () { sinon.stub(indexPattern, 'getInterval').returns(false); sinon.stub(indexPattern, 'hasTimeField').returns(true); sinon.stub(indexPattern, 'isWildcard').returns(true); sinon.stub(indexPattern, 'canExpandIndices').returns(false); }); it('is fulfilled by id', async function () { const indexList = await indexPattern.toIndexList(); expect(indexList).to.equal(indexPattern.id); }); }); describe('when index pattern is neither an interval nor a time-based wildcard', function () { beforeEach(function () { sinon.stub(indexPattern, 'getInterval').returns(false); }); it('is fulfilled by id', async function () { const indexList = await indexPattern.toIndexList(); expect(indexList).to.equal(indexPattern.id); }); }); }); describe('#canExpandIndices()', function () { it('returns true if notExpandable is false', function () { indexPattern.notExpandable = false; expect(indexPattern.canExpandIndices()).to.be(true); }); it('returns true if notExpandable is not defined', function () { delete indexPattern.notExpandable; expect(indexPattern.canExpandIndices()).to.be(true); }); it('returns false if notExpandable is true', function () { indexPattern.notExpandable = true; expect(indexPattern.canExpandIndices()).to.be(false); }); }); describe('#hasTimeField()', function () { beforeEach(function () { // for the sake of these tests, it doesn't much matter what type of field // this is so long as it exists indexPattern.timeFieldName = 'bytes'; }); it('returns false if no time field', function () { delete indexPattern.timeFieldName; expect(indexPattern.hasTimeField()).to.be(false); }); it('returns false if time field does not actually exist in fields', function () { indexPattern.timeFieldName = 'does not exist'; expect(indexPattern.hasTimeField()).to.be(false); }); it('returns true if valid time field is configured', function () { expect(indexPattern.hasTimeField()).to.be(true); }); }); describe('#isWildcard()', function () { it('returns true if id has an *', function () { indexPattern.id = 'foo*'; expect(indexPattern.isWildcard()).to.be(true); }); it('returns false if id has no *', function () { indexPattern.id = 'foo'; expect(indexPattern.isWildcard()).to.be(false); }); }); });
describe('Courier DocFetchRequest class', function () { let storage; let source; let defer; let req; let setVersion; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private, Promise, $injector) { const DocSource = Private(DocSourceProvider); const DocFetchRequest = Private(DocRequestProvider); storage = $injector.get('localStorage').store = $injector.get('sessionStorage').store = { getItem: sinon.stub(), setItem: sinon.stub(), removeItem: sinon.stub(), clear: sinon.stub() }; source = new DocSource({}) .set('index', 'doc-index') .set('type', 'doc-type') .set('id', 'doc-id'); defer = Promise.defer(); req = new DocFetchRequest(source, defer); /** * Setup the version numbers for tests. There are two versions for the * purposes of these tests. * * @param {number} mine - the version that the DocSource most * recently received from elasticsearch. * @param {number} theirs - the version that other DocSources have * received from elasticsearfch. */ setVersion = function (mine, theirs) { source._version = mine; storage.getItem.withArgs(source._versionKey()).returns(theirs); }; })); describe('#canStart', function () { it('can if the doc is unknown', function () { setVersion(undefined, undefined); expect(req.canStart()).to.be(true); }); it('cannot if the doc is unknown but the request is already in progress', function () { setVersion(undefined, undefined); req.start(); expect(req.canStart()).to.be(false); }); it('can if the doc is out of date', function () { setVersion(1, 2); expect(req.canStart()).to.be(true); }); it('can if the doc is out of date and the request is in progress', function () { setVersion(1, 2); req.start(); expect(req.canStart()).to.be(true); }); it('cannot if the doc is up to date', function () { setVersion(2, 2); expect(req.canStart()).to.be(false); }); it('can if the doc is overdated', function () { setVersion(5, 2); expect(req.canStart()).to.be(true); }); it('can if shared version is cleared', function () { setVersion(10, undefined); expect(req.canStart()).to.be(true); }); it('can if everyone else has a doc', function () { setVersion(undefined, 10); expect(req.canStart()).to.be(true); }); }); });
describe('date_histogram', function () { let vis; let agg; let field; let filter; let bucketKey; let bucketStart; let intervalOptions; let init; beforeEach(ngMock.module('kibana', function ($provide) { $provide.constant('kbnDefaultAppId', ''); })); beforeEach(ngMock.inject(function (Private) { const Vis = Private(VisProvider); const indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); const createFilter = Private(AggTypesBucketsCreateFilterDateHistogramProvider); intervalOptions = Private(AggTypesBucketsIntervalOptionsProvider); init = function (interval, duration) { interval = interval || 'auto'; if (interval === 'custom') interval = agg.params.customInterval; duration = duration || moment.duration(15, 'minutes'); field = _.sample(_.reject(indexPattern.fields.byType.date, 'scripted')); vis = new Vis(indexPattern, { type: 'histogram', aggs: [ { type: 'date_histogram', schema: 'segment', params: { field: field.name, interval: interval, customInterval: '5d' } } ] }); agg = vis.aggs[0]; bucketKey = _.sample(aggResp.aggregations['1'].buckets).key; bucketStart = moment(bucketKey); const timePad = moment.duration(duration / 2); agg.buckets.setBounds({ min: bucketStart.clone().subtract(timePad), max: bucketStart.clone().add(timePad), }); agg.buckets.setInterval(interval); filter = createFilter(agg, bucketKey); }; })); it('creates a valid range filter', function () { init(); expect(filter).to.have.property('range'); expect(filter.range).to.have.property(field.name); const fieldParams = filter.range[field.name]; expect(fieldParams).to.have.property('gte'); expect(fieldParams.gte).to.be.a('number'); expect(fieldParams).to.have.property('lt'); expect(fieldParams.lt).to.be.a('number'); expect(fieldParams).to.have.property('format'); expect(fieldParams.format).to.be('epoch_millis'); expect(fieldParams.gte).to.be.lessThan(fieldParams.lt); expect(filter).to.have.property('meta'); expect(filter.meta).to.have.property('index', vis.indexPattern.id); }); it('extends the filter edge to 1ms before the next bucket for all interval options', function () { intervalOptions.forEach(function (option) { let duration; if (option.val !== 'custom' && moment(1, option.val).isValid()) { duration = moment.duration(10, option.val); if (+duration < 10) { throw new Error('unable to create interval for ' + option.val); } } init(option.val, duration); const interval = agg.buckets.getInterval(); const params = filter.range[field.name]; expect(params.gte).to.be(+bucketStart); expect(params.lt).to.be(+bucketStart.clone().add(interval)); }); }); });
describe('AggTableGroup Directive', function () { let $rootScope; let $compile; let tabifyAggResponse; let Vis; let indexPattern; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function ($injector, Private) { tabifyAggResponse = Private(AggResponseTabifyTabifyProvider); indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); Vis = Private(VisProvider); $rootScope = $injector.get('$rootScope'); $compile = $injector.get('$compile'); })); let $scope; beforeEach(function () { $scope = $rootScope.$new(); }); afterEach(function () { $scope.$destroy(); }); it('renders a simple split response properly', function () { const vis = new Vis(indexPattern, 'table'); $scope.group = tabifyAggResponse(vis, fixtures.metricOnly); $scope.sort = { columnIndex: null, direction: null }; const $el = $('<kbn-agg-table-group group="group"></kbn-agg-table-group>'); $compile($el)($scope); $scope.$digest(); // should create one sub-tbale expect($el.find('kbn-agg-table').size()).to.be(1); }); it('renders nothing if the table list is empty', function () { const $el = $('<kbn-agg-table-group group="group"></kbn-agg-table-group>'); $scope.group = { tables: [] }; $compile($el)($scope); $scope.$digest(); const $subTables = $el.find('kbn-agg-table'); expect($subTables.size()).to.be(0); }); it('renders a complex response properly', function () { const vis = new Vis(indexPattern, { type: 'pie', aggs: [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, { type: 'terms', schema: 'split', params: { field: 'extension' } }, { type: 'terms', schema: 'segment', params: { field: 'geo.src' } }, { type: 'terms', schema: 'segment', params: { field: 'machine.os' } } ] }); vis.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); }); const group = $scope.group = tabifyAggResponse(vis, fixtures.threeTermBuckets); const $el = $('<kbn-agg-table-group group="group"></kbn-agg-table-group>'); $compile($el)($scope); $scope.$digest(); const $subTables = $el.find('kbn-agg-table'); expect($subTables.size()).to.be(3); const $subTableHeaders = $el.find('.agg-table-group-header'); expect($subTableHeaders.size()).to.be(3); $subTableHeaders.each(function (i) { expect($(this).text()).to.be(group.tables[i].title); }); }); });
describe('Events', function () { require('test_utils/no_digest_promises').activateForSuite(); let Events; let Promise; let eventsInstance; beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function ($injector, Private) { Promise = $injector.get('Promise'); Events = Private(EventsProvider); eventsInstance = new Events(); })); it('should handle on events', function () { const obj = new Events(); const prom = obj.on('test', function (message) { expect(message).to.equal('Hello World'); }); obj.emit('test', 'Hello World'); return prom; }); it('should work with inherited objects', function () { createLegacyClass(MyEventedObject).inherits(Events); function MyEventedObject() { MyEventedObject.Super.call(this); } const obj = new MyEventedObject(); const prom = obj.on('test', function (message) { expect(message).to.equal('Hello World'); }); obj.emit('test', 'Hello World'); return prom; }); it('should clear events when off is called', function () { const obj = new Events(); obj.on('test', _.noop); expect(obj._listeners).to.have.property('test'); expect(obj._listeners.test).to.have.length(1); obj.off(); expect(obj._listeners).to.not.have.property('test'); }); it('should clear a specific handler when off is called for an event', function () { const obj = new Events(); const handler1 = sinon.stub(); const handler2 = sinon.stub(); obj.on('test', handler1); obj.on('test', handler2); expect(obj._listeners).to.have.property('test'); obj.off('test', handler1); return obj.emit('test', 'Hello World') .then(function () { sinon.assert.calledOnce(handler2); sinon.assert.notCalled(handler1); }); }); it('should clear a all handlers when off is called for an event', function () { const obj = new Events(); const handler1 = sinon.stub(); obj.on('test', handler1); expect(obj._listeners).to.have.property('test'); obj.off('test'); expect(obj._listeners).to.not.have.property('test'); return obj.emit('test', 'Hello World') .then(function () { sinon.assert.notCalled(handler1); }); }); it('should handle multiple identical emits in the same tick', function () { const obj = new Events(); const handler1 = sinon.stub(); obj.on('test', handler1); const emits = [ obj.emit('test', 'one'), obj.emit('test', 'two'), obj.emit('test', 'three') ]; return Promise .all(emits) .then(function () { expect(handler1.callCount).to.be(emits.length); expect(handler1.getCall(0).calledWith('one')).to.be(true); expect(handler1.getCall(1).calledWith('two')).to.be(true); expect(handler1.getCall(2).calledWith('three')).to.be(true); }); }); it('should handle emits from the handler', function () { const obj = new Events(); const secondEmit = Promise.defer(); const handler1 = sinon.spy(function () { if (handler1.calledTwice) { return; } obj.emit('test').then(_.bindKey(secondEmit, 'resolve')); }); obj.on('test', handler1); return Promise .all([ obj.emit('test'), secondEmit.promise ]) .then(function () { expect(handler1.callCount).to.be(2); }); }); it('should only emit to handlers registered before emit is called', function () { const obj = new Events(); const handler1 = sinon.stub(); const handler2 = sinon.stub(); obj.on('test', handler1); const emits = [ obj.emit('test', 'one'), obj.emit('test', 'two'), obj.emit('test', 'three') ]; return Promise.all(emits).then(function () { expect(handler1.callCount).to.be(emits.length); obj.on('test', handler2); const emits2 = [ obj.emit('test', 'four'), obj.emit('test', 'five'), obj.emit('test', 'six') ]; return Promise.all(emits2) .then(function () { expect(handler1.callCount).to.be(emits.length + emits2.length); expect(handler2.callCount).to.be(emits2.length); }); }); }); it('should pass multiple arguments from the emitter', function () { const obj = new Events(); const handler = sinon.stub(); const payload = [ 'one', { hello: 'tests' }, null ]; obj.on('test', handler); return obj.emit('test', payload[0], payload[1], payload[2]) .then(function () { expect(handler.callCount).to.be(1); expect(handler.calledWithExactly(payload[0], payload[1], payload[2])).to.be(true); }); }); it('should preserve the scope of the handler', function () { const obj = new Events(); const expected = 'some value'; let testValue; function handler() { testValue = this.getVal(); } handler.getVal = _.constant(expected); obj.on('test', handler); return obj.emit('test') .then(function () { expect(testValue).to.equal(expected); }); }); it('should always emit in the same order', function () { const handler = sinon.stub(); const obj = new Events(); obj.on('block', _.partial(handler, 'block')); obj.on('last', _.partial(handler, 'last')); return Promise .all([ obj.emit('block'), obj.emit('block'), obj.emit('block'), obj.emit('block'), obj.emit('block'), obj.emit('block'), obj.emit('block'), obj.emit('block'), obj.emit('block'), obj.emit('last') ]) .then(function () { expect(handler.callCount).to.be(10); handler.args.forEach(function (args, i) { expect(args[0]).to.be(i < 9 ? 'block' : 'last'); }); }); }); it('calls emitted handlers asynchronously', (done) => { const listenerStub = sinon.stub(); eventsInstance.on('test', listenerStub); eventsInstance.emit('test'); sinon.assert.notCalled(listenerStub); setTimeout(() => { sinon.assert.calledOnce(listenerStub); done(); }, 100); }); it('calling off after an emit that has not yet triggered the handler, will not call the handler', (done) => { const listenerStub = sinon.stub(); eventsInstance.on('test', listenerStub); eventsInstance.emit('test'); // It's called asynchronously so it shouldn't be called yet. sinon.assert.notCalled(listenerStub); eventsInstance.off('test', listenerStub); setTimeout(() => { sinon.assert.notCalled(listenerStub); done(); }, 100); }); });