Skip to content

Commit

Permalink
brush histogram 2 (elastic#9140)
Browse files Browse the repository at this point in the history
Backports PR elastic#9039

**Commit 1:**
support scripted fields

* Original sha: 4a6e9fe
* Authored by nreese <[email protected]> on 2016-11-10T22:35:23Z
* Committed by CJ Cenizal <[email protected]> on 2016-11-10T23:30:53Z

**Commit 2:**
update column_chart test to check brush for ordered data

* Original sha: 97aab8a
* Authored by nreese <[email protected]> on 2016-11-10T23:06:29Z
* Committed by CJ Cenizal <[email protected]> on 2016-11-10T23:32:28Z

**Commit 3:**
brush histogram

* Original sha: 3b6652f
* Authored by nreese <[email protected]> on 2016-10-18T20:42:23Z
* Committed by CJ Cenizal <[email protected]> on 2016-11-10T23:33:27Z

**Commit 4:**
incorporate changes request by cjcenizal

* Original sha: 0f7e70a
* Authored by nreese <[email protected]> on 2016-11-01T22:08:38Z
* Committed by CJ Cenizal <[email protected]> on 2016-11-10T23:33:43Z

**Commit 5:**
fixed typo

* Original sha: 48e5bd6
* Authored by nreese <[email protected]> on 2016-11-02T03:39:41Z
* Committed by CJ Cenizal <[email protected]> on 2016-11-10T23:33:52Z

**Commit 6:**
support scripted fields

* Original sha: 4a27881
* Authored by nreese <[email protected]> on 2016-11-10T22:35:23Z
* Committed by CJ Cenizal <[email protected]> on 2016-11-10T23:34:00Z

**Commit 7:**
update column_chart test to check brush for ordered data

* Original sha: 77c295a
* Authored by nreese <[email protected]> on 2016-11-10T23:06:29Z
* Committed by CJ Cenizal <[email protected]> on 2016-11-10T23:35:31Z

**Commit 8:**
Fix init_x_axis unit tests.

* Original sha: 32d9ce1
* Authored by CJ Cenizal <[email protected]> on 2016-11-11T22:13:06Z

**Commit 9:**
add brush_event test

* Original sha: 22807f2
* Authored by nreese <[email protected]> on 2016-11-16T03:56:35Z
* Committed by CJ Cenizal <[email protected]> on 2016-11-16T18:34:28Z

**Commit 10:**
Fix small timezone bug in brushEvent tests. Polish test descriptions.

* Original sha: 28c8a6a
* Authored by CJ Cenizal <[email protected]> on 2016-11-16T19:20:43Z
  • Loading branch information
elastic-jasper authored and cjcenizal committed Nov 18, 2016
1 parent 2b04dde commit d8fda1e
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function visualizationLoader(savedVisualizations, Private) { // I
.then(function (savedVis) {
// $scope.state comes via $scope inheritence from the dashboard app. Don't love this.
savedVis.vis.listeners.click = filterBarClickHandler($scope.state);
savedVis.vis.listeners.brush = brushEvent;
savedVis.vis.listeners.brush = brushEvent($scope.state);

return {
savedObj: savedVis,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'ui/visualize';
import 'ui/doc_table';
import PluginsKibanaDashboardComponentsPanelLibLoadPanelProvider from 'plugins/kibana/dashboard/components/panel/lib/load_panel';
import FilterManagerProvider from 'ui/filter_manager';
import UtilsBrushEventProvider from 'ui/utils/brush_event';
import uiModules from 'ui/modules';
import panelTemplate from 'plugins/kibana/dashboard/components/panel/panel.html';
uiModules
Expand All @@ -23,9 +22,6 @@ uiModules
};
});


const brushEvent = Private(UtilsBrushEventProvider);

const getPanelId = function (panel) {
return ['P', panel.panelIndex].join('-');
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ function discoverController($scope, config, courier, $route, $window, Notifier,
timefilter.time.to = moment(e.point.x + e.data.ordered.interval);
timefilter.time.mode = 'absolute';
},
brush: brushEvent
brush: brushEvent($scope.state)
},
aggs: visStateAggs
});
Expand Down
2 changes: 1 addition & 1 deletion src/core_plugins/kibana/public/visualize/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ function VisEditor($scope, $route, timefilter, AppState, $location, kbnUrl, $tim
$scope.$on('$destroy', () => stateMonitor.destroy());

editableVis.listeners.click = vis.listeners.click = filterBarClickHandler($state);
editableVis.listeners.brush = vis.listeners.brush = brushEvent;
editableVis.listeners.brush = vis.listeners.brush = brushEvent($state);

// track state of editable vis vs. "actual" vis
$scope.stageEditableVis = transferVisState(editableVis, vis, true);
Expand Down
18 changes: 18 additions & 0 deletions src/ui/public/agg_response/point_series/__tests__/_init_x_axis.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ describe('initXAxis', function () {
}
}
};
const field = {};
const indexPattern = {};

it('sets the xAxisFormatter if the agg is not ordered', function () {
let chart = _.cloneDeep(baseChart);
Expand All @@ -37,11 +39,19 @@ describe('initXAxis', function () {
it('makes the chart ordered if the agg is ordered', function () {
let chart = _.cloneDeep(baseChart);
chart.aspects.x.agg.type.ordered = true;
chart.aspects.x.agg.params = {
field: field
};
chart.aspects.x.agg.vis = {
indexPattern: indexPattern
};

initXAxis(chart);
expect(chart)
.to.have.property('xAxisLabel', 'label')
.and.have.property('xAxisFormatter', chart.aspects.x.agg.fieldFormatter())
.and.have.property('indexPattern', indexPattern)
.and.have.property('xAxisField', field)
.and.have.property('ordered');

expect(chart.ordered)
Expand All @@ -53,11 +63,19 @@ describe('initXAxis', function () {
let chart = _.cloneDeep(baseChart);
chart.aspects.x.agg.type.ordered = true;
chart.aspects.x.agg.write = _.constant({ params: { interval: 10 } });
chart.aspects.x.agg.params = {
field: field
};
chart.aspects.x.agg.vis = {
indexPattern: indexPattern
};

initXAxis(chart);
expect(chart)
.to.have.property('xAxisLabel', 'label')
.and.have.property('xAxisFormatter', chart.aspects.x.agg.fieldFormatter())
.and.have.property('indexPattern', indexPattern)
.and.have.property('xAxisField', field)
.and.have.property('ordered');

expect(chart.ordered)
Expand Down
3 changes: 3 additions & 0 deletions src/ui/public/agg_response/point_series/_init_x_axis.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ define(function () {

if (!x.agg || !x.agg.type.ordered) return;

chart.indexPattern = x.agg.vis.indexPattern;
chart.xAxisField = x.agg.params.field;

chart.ordered = {};
let xAggOutput = x.agg.write();
if (xAggOutput.params.interval) {
Expand Down
177 changes: 177 additions & 0 deletions src/ui/public/utils/__tests__/brush_event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import _ from 'lodash';
import expect from 'expect.js';
import moment from 'moment';
import ngMock from 'ng_mock';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import UtilsBrushEventProvider from 'ui/utils/brush_event';

describe('brushEvent', function () {
let brushEventFn;
let timefilter;

beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private, $injector, _timefilter_) {
brushEventFn = Private(UtilsBrushEventProvider);
timefilter = _timefilter_;
}));

it('is a function that returns a function', function () {
expect(brushEventFn).to.be.a(Function);
expect(brushEventFn({})).to.be.a(Function);
});

describe('returned function', function () {
let $state;
let brushEvent;

const baseState = {
filters:[],
};

const baseEvent = {
data: {
fieldFormatter: _.constant({}),
},
};

beforeEach(ngMock.inject(function (Private, $injector) {
baseEvent.data.indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
$state = _.cloneDeep(baseState);
brushEvent = brushEventFn($state);
}));

it('should be a function', function () {
expect(brushEvent).to.be.a(Function);
});

it('ignores event when data.xAxisField not provided', function () {
const event = _.cloneDeep(baseEvent);
brushEvent(event);
expect($state)
.not.have.property('$newFilters');
});

describe('handles an event when the x-axis field is a date', function () {
let dateEvent;
const dateField = {
name: 'dateField',
type: 'date'
};

beforeEach(ngMock.inject(function (Private, $injector) {
dateEvent = _.cloneDeep(baseEvent);
dateEvent.data.xAxisField = dateField;
}));

it('by ignoring the event when range spans zero time', function () {
const event = _.cloneDeep(dateEvent);
event.range = [1388559600000, 1388559600000];
brushEvent(event);
expect($state)
.not.have.property('$newFilters');
});

it('by updating the timefilter', function () {
const event = _.cloneDeep(dateEvent);
const DAY_IN_MS = 24 * 60 * 60 * 1000;
event.range = [1388559600000, 1388559600000 + DAY_IN_MS];
brushEvent(event);
expect(timefilter.time.mode).to.be('absolute');
expect(moment.isMoment(timefilter.time.from))
.to.be(true);
// Set to a baseline timezone for comparison.
expect(timefilter.time.from.utcOffset(0).format('YYYY-MM-DD'))
.to.equal('2014-01-01');
expect(moment.isMoment(timefilter.time.to))
.to.be(true);
// Set to a baseline timezone for comparison.
expect(timefilter.time.to.utcOffset(0).format('YYYY-MM-DD'))
.to.equal('2014-01-02');
});
});

describe('handles an event when the x-axis field is a number', function () {
let numberEvent;
const numberField = {
name: 'numberField',
type: 'number'
};

beforeEach(ngMock.inject(function (Private, $injector) {
numberEvent = _.cloneDeep(baseEvent);
numberEvent.data.xAxisField = numberField;
}));

it('by ignoring the event when range does not span at least 2 values', function () {
const event = _.cloneDeep(numberEvent);
event.range = [1];
brushEvent(event);
expect($state)
.not.have.property('$newFilters');
});

it('by creating a new filter', function () {
const event = _.cloneDeep(numberEvent);
event.range = [1,2,3,4];
brushEvent(event);
expect($state)
.to.have.property('$newFilters');
expect($state.filters.length)
.to.equal(0);
expect($state.$newFilters.length)
.to.equal(1);
expect($state.$newFilters[0].range.numberField.gte)
.to.equal(1);
expect($state.$newFilters[0].range.numberField.lt)
.to.equal(4);
});

it('by updating the existing range filter', function () {
const event = _.cloneDeep(numberEvent);
event.range = [3,7];
$state.filters.push({
meta: {
key: 'numberField'
},
range: {gte: 1, lt: 4}
});
brushEvent(event);
expect($state)
.not.have.property('$newFilters');
expect($state.filters.length)
.to.equal(1);
expect($state.filters[0].range.numberField.gte)
.to.equal(3);
expect($state.filters[0].range.numberField.lt)
.to.equal(7);
});

it('by updating the existing scripted filter', function () {
const event = _.cloneDeep(numberEvent);
event.range = [3,7];
$state.filters.push({
meta: {
key: 'numberField'
},
script: {
script: {
params: {
gte: 1,
lt: 4
}
}
}
});
brushEvent(event);
expect($state)
.not.have.property('$newFilters');
expect($state.filters.length)
.to.equal(1);
expect($state.filters[0].script.script.params.gte)
.to.equal(3);
expect($state.filters[0].script.script.params.lt)
.to.equal(7);
});
});
});
});
52 changes: 45 additions & 7 deletions src/ui/public/utils/brush_event.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,51 @@
import _ from 'lodash';
import moment from 'moment';
import buildRangeFilter from 'ui/filter_manager/lib/range';
export default function brushEventProvider(timefilter) {
return function (event) {
let from = moment(event.range[0]);
let to = moment(event.range[1]);
return $state => {
return event => {
if (!event.data.xAxisField) {
return;
}

if (to - from === 0) return;
switch (event.data.xAxisField.type) {
case 'date':
let from = moment(event.range[0]);
let to = moment(event.range[1]);

timefilter.time.from = from;
timefilter.time.to = to;
timefilter.time.mode = 'absolute';
if (to - from === 0) return;

timefilter.time.from = from;
timefilter.time.to = to;
timefilter.time.mode = 'absolute';
break;

case 'number':
if (event.range.length <= 1) return;

const existingFilter = $state.filters.find(filter => (
filter.meta && filter.meta.key === event.data.xAxisField.name
));

const min = event.range[0];
const max = event.range[event.range.length - 1];
const range = {gte: min, lt: max};
if (_.has(existingFilter, 'range')) {
existingFilter.range[event.data.xAxisField.name] = range;
} else if (_.has(existingFilter, 'script.script.params.gte')
&& _.has(existingFilter, 'script.script.params.lt')) {
existingFilter.script.script.params.gte = min;
existingFilter.script.script.params.lt = max;
} else {
const newFilter = buildRangeFilter(
event.data.xAxisField,
range,
event.data.indexPattern,
event.data.xAxisFormatter);
$state.$newFilters = [newFilter];
}
break;
}
};
};
};
10 changes: 5 additions & 5 deletions src/ui/public/vislib/__tests__/visualizations/column_chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import series from 'fixtures/vislib/mock_data/date_histogram/_series';
import seriesPosNeg from 'fixtures/vislib/mock_data/date_histogram/_series_pos_neg';
import seriesNeg from 'fixtures/vislib/mock_data/date_histogram/_series_neg';
import termsColumns from 'fixtures/vislib/mock_data/terms/_columns';
//const histogramRows = require('fixtures/vislib/mock_data/histogram/_rows');
import histogramRows from 'fixtures/vislib/mock_data/histogram/_rows';
import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series';
import $ from 'jquery';
import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture';
Expand All @@ -20,7 +20,7 @@ const dataTypesArray = [
['series with positive and negative values', 'stacked', seriesPosNeg],
['series with negative values', 'stacked', seriesNeg],
['terms columns', 'grouped', termsColumns],
// ['histogram rows', 'percentage', histogramRows],
['histogram rows', 'percentage', histogramRows],
['stackedSeries', 'stacked', stackedSeries],
];

Expand Down Expand Up @@ -116,12 +116,12 @@ dataTypesArray.forEach(function (dataType, i) {
};
}

it('should attach the brush if data is a set of ordered dates', function () {
it('should attach the brush if data is a set is ordered', 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);
const allowBrushing = Boolean(ordered);
expect(has.brush).to.be(allowBrushing);
});
});

Expand Down
Loading

0 comments on commit d8fda1e

Please sign in to comment.