Merge pull request #2851 from spenceralger/discoverHistory

Fix history in Discover
This commit is contained in:
Joe Fleming 2015-02-05 11:59:07 -07:00
commit fa60c3fc30
2 changed files with 131 additions and 132 deletions

View file

@ -7,7 +7,7 @@ define(function (require) {
* @param {object} indexPattern used for determining default sort
* @returns {object} a sort object suitable for returning to elasticsearch
*/
return function (sort, indexPattern) {
function getSort(sort, indexPattern) {
var sortObj = {};
var field, direction;
@ -33,5 +33,11 @@ define(function (require) {
return sortObj;
}
getSort.array = function (sort, indexPattern) {
return _(getSort(sort, indexPattern)).pairs().pop();
};
return getSort;
});

View file

@ -6,6 +6,7 @@ define(function (require) {
var onlyDisabled = require('components/filter_bar/lib/onlyDisabled');
var filterManager = require('components/filter_manager/filter_manager');
var getSort = require('components/doc_table/lib/get_sort');
var rison = require('utils/rison');
var datemath = require('utils/datemath');
@ -32,8 +33,25 @@ define(function (require) {
template: require('text!plugins/discover/index.html'),
reloadOnSearch: false,
resolve: {
indexList: function (courier) {
return courier.indexPatterns.getIds();
ip: function (Promise, courier, config, $location) {
return courier.indexPatterns.getIds()
.then(function (list) {
var stateRison = $location.search()._a;
var state;
try { state = rison.decode(stateRison); } catch (e) {}
state = state || {};
var specified = !!state.index;
var exists = _.contains(list, state.index);
var id = exists ? state.index : config.get('defaultIndex');
return Promise.props({
list: list,
loaded: courier.indexPatterns.get(id),
stateVal: state.index,
stateValFound: specified && exists
});
});
},
savedSearch: function (courier, savedSearches, $route) {
return savedSearches.get($route.current.params.id)
@ -51,7 +69,6 @@ define(function (require) {
var Vis = Private(require('components/vis/vis'));
var docTitle = Private(require('components/doc_title/doc_title'));
var brushEvent = Private(require('utils/brush_event'));
var HitSortFn = Private(require('plugins/discover/_hit_sort_fn'));
var notify = new Notifier({
@ -71,63 +88,46 @@ define(function (require) {
var savedSearch = $route.current.locals.savedSearch;
$scope.$on('$destroy', savedSearch.destroy);
// list of indexPattern id's
var indexPatternList = $route.current.locals.indexList;
// the actual courier.SearchSource
$scope.searchSource = savedSearch.searchSource;
$scope.indexPattern = resolveIndexPatternLoading();
$scope.searchSource.set('index', $scope.indexPattern);
if (savedSearch.id) {
docTitle.change(savedSearch.title);
}
var $state = $scope.state = new AppState(getStateDefaults());
function getStateDefaults() {
return {
query: $scope.searchSource.get('query') || '',
sort: _.pairs(getSort(savedSearch.sort, $scope.searchSource.get('index'))).pop(),
sort: getSort.array(savedSearch.sort, $scope.indexPattern),
columns: savedSearch.columns || ['_source'],
index: $scope.searchSource.get('index').id || config.get('defaultIndex'),
index: $scope.indexPattern.id,
interval: 'auto',
filters: _.cloneDeep($scope.searchSource.get('filter'))
};
}
$state.sort = getSort.array($state.sort, $scope.indexPattern);
// force the index pattern based on what we could fetch
$scope.searchSource.set('index', $scope.indexPattern);
$state.index = $scope.indexPattern.id;
var metaFields = config.get('metaFields');
filterManager.init($state);
if (!_.contains(indexPatternList, $state.index)) {
var reason = 'The index specified in the URL is not a configured pattern. ';
var defaultIndex = config.get('defaultIndex');
if (defaultIndex) {
notify.warning(reason + 'Updated it to use the default: "' + defaultIndex + '"');
$state.index = config.get('defaultIndex');
} else {
notify.warning(reason + 'Please set a default index to continue.');
kbnUrl.redirect('/settings/indices');
return;
}
}
$scope.opts = {
// number of records to fetch, then paginate through
sampleSize: config.get('discover:sampleSize'),
// Index to match
index: $state.index,
index: $scope.indexPattern.id,
timefield: $scope.indexPattern.timeFieldName,
savedSearch: savedSearch,
indexPatternList: indexPatternList,
changeIndexAndReload: function () {
$state.index = $scope.opts.index;
$state.save();
$route.reload();
}
indexPatternList: $route.current.locals.ip.list
};
// stores the complete list of fields
$scope.fields = null;
var init = _.once(function () {
var showTotal = 5;
$scope.failuresShown = showTotal;
@ -137,29 +137,47 @@ define(function (require) {
$scope.showLessFailures = function () {
$scope.failuresShown = showTotal;
};
return $scope.updateDataSource()
.then(function () {
setFields();
// state fields that shouldn't trigger a fetch when changed
var ignoreStateChanges = ['columns'];
// stores the complete list of fields
$scope.fields = _($scope.indexPattern.fields)
.sortBy('name')
.transform(function (fields, field) {
// clone the field with Object.create so that it's getters
// and non-enumerable props are preserved
var clone = Object.create(field);
clone.display = _.contains($state.columns, field.name);
clone.rowCount = $scope.rows ? $scope.rows.fieldCounts[field.name] : 0;
fields.push(clone);
}, [])
.value();
// listen for changes, and relisten everytime something happens
$scope.$listen($state, 'fetch_with_changes', updateFields);
$scope.$listen($state, 'reset_with_changes', updateFields);
refreshColumns();
function updateFields(changed) {
if (_.contains(changed, 'columns')) {
$scope.fields.forEach(function (field) {
field.display = _.contains($state.columns, field.name);
});
refreshColumns();
}
// listen for changes, and relisten everytime something happens
$scope.$listen($state, 'fetch_with_changes', updateFields);
$scope.$listen($state, 'reset_with_changes', updateFields);
function updateFields(changes) {
var newColumns = _.contains(changes, 'columns');
var newIndex = _.contains(changes, 'index');
var otherChanges = _.pull(changes, 'index', 'columns');
// if we only have ignorable changes, do nothing
if (_.difference(changed, ignoreStateChanges).length) $scope.fetch();
if (newIndex) {
// we will be reloading, don't need to juggle state
return;
}
if (newColumns) {
$scope.fields.forEach(function (field) {
field.display = _.contains($state.columns, field.name);
});
refreshColumns();
}
if (otherChanges.length) $scope.fetch();
}
$scope.updateDataSource()
.then(function () {
$scope.$listen(timefilter, 'update', function () {
$scope.fetch();
});
@ -175,10 +193,13 @@ define(function (require) {
});
$scope.$watch('state.filters', function (newFilters, oldFilters) {
if (newFilters === oldFilters) return;
if (onlyDisabled(newFilters, oldFilters)) {
$state.save();
return;
}
$scope.fetch();
});
@ -186,6 +207,18 @@ define(function (require) {
timefilter.enabled = !!timefield;
});
$scope.$watch('opts.index', changeIndexPattern($scope.opts, $state));
$scope.$watch('state.index', changeIndexPattern($state, $scope.opts));
function changeIndexPattern(from, to) {
return function () {
if (from.index === to.index) return;
to.index = from.index;
if (to === $state) $state.save();
$route.reload();
};
}
$scope.$watchMulti([
'rows',
'fetchStatus'
@ -229,17 +262,16 @@ define(function (require) {
notify.error('An error occured with your request. Reset your inputs and try again.');
}).catch(notify.fatal);
if ($scope.opts.timefield) {
setupVisualization().then(function () {
$scope.updateTime();
init.complete = true;
$scope.$emit('application.load');
});
} else {
init.complete = true;
$scope.$emit('application.load');
function initForTime() {
return setupVisualization().then($scope.updateTime);
}
return Promise.resolve($scope.opts.timefield && initForTime())
.then(function () {
init.complete = true;
$state.replace();
$scope.$emit('application.load');
});
});
});
@ -299,7 +331,7 @@ define(function (require) {
if (!$scope.rows) flushResponseData();
var sort = $state.sort;
var timeField = $scope.searchSource.get('index').timeFieldName;
var timeField = $scope.indexPattern.timeFieldName;
var totalSize = $scope.size || $scope.opts.sampleSize;
/**
@ -366,15 +398,14 @@ define(function (require) {
if (hit.$$_formatted && !sortFn) return;
// Flatten the fields
var indexPattern = $scope.searchSource.get('index');
hit.$$_flattened = indexPattern.flattenHit(hit);
hit.$$_flattened = $scope.indexPattern.flattenHit(hit);
var formatAndCount = function (value, name) {
// add up the counts for each field name
counts[name] = counts[name] ? counts[name] + 1 : 1;
var defaultFormat = courier.indexPatterns.fieldFormats.defaultByType.string;
var field = indexPattern.fields.byName[name];
var field = $scope.indexPattern.fields.byName[name];
var formatter = (field && field.format) ? field.format : defaultFormat;
return formatter.convert(value);
@ -419,15 +450,10 @@ define(function (require) {
kbnUrl.change('/discover');
};
$scope.updateDataSource = function () {
var chartOptions;
$scope.updateDataSource = Promise.method(function () {
$scope.searchSource
.size($scope.opts.sampleSize)
.sort(function () {
var sort = getSort($state.sort, $scope.indexPattern);
$state.sort = _.pairs(sort)[0];
return sort;
})
.sort(getSort($state.sort, $scope.indexPattern))
.query(!$state.query ? null : $state.query)
.highlight({
pre_tags: [highlightTags.pre],
@ -435,27 +461,7 @@ define(function (require) {
fields: {'*': {}}
})
.set('filter', $state.filters || []);
$state.index = $scope.opts.index = $scope.opts.index || config.get('defaultIndex');
return courier.indexPatterns.get($scope.opts.index).then(function (pattern) {
$scope.indexPattern = pattern;
$scope.opts.timefield = $scope.indexPattern.timeFieldName;
// did we update the index pattern?
var refresh = $scope.indexPattern !== $scope.searchSource.get('index');
// make sure the pattern is set on the "leaf" searchSource, not just the root
$scope.searchSource.set('index', pattern);
if (refresh) {
delete $state.sort;
delete $scope.fields;
delete $scope.columns;
setFields();
}
});
};
});
// This is a hacky optimization for comparing the contents of a large array to a short one.
function arrayToKeys(array, value) {
@ -466,41 +472,9 @@ define(function (require) {
return obj;
}
function setFields() {
var indexPattern = $scope.searchSource.get('index');
var currentState = _.transform($scope.fields || [], function (current, field) {
current[field.name] = {
display: field.display
};
}, {});
var columnObjects = arrayToKeys($state.columns);
$scope.fields = [];
$scope.fieldsByName = {};
if (!indexPattern) return;
_.sortBy(indexPattern.fields, 'name').forEach(function (field) {
_.defaults(field, currentState[field.name]);
// clone the field with Object.create so that it's getters
// and non-enumerable props are preserved
var clone = Object.create(field);
clone.display = columnObjects[field.name] || false;
clone.rowCount = $scope.rows ? $scope.rows.fieldCounts[field.name] : 0;
$scope.fields.push(clone);
$scope.fieldsByName[field.name] = clone;
});
refreshColumns();
}
// TODO: On array fields, negating does not negate the combination, rather all terms
$scope.filterQuery = function (field, values, operation) {
var indexPattern = $scope.searchSource.get('index');
indexPattern.popularizeField(field, 1);
$scope.indexPattern.popularizeField(field, 1);
filterManager.add(field, values, operation, $state.index);
};
@ -533,22 +507,20 @@ define(function (require) {
function refreshColumns() {
// Get all displayed field names;
var fields = _.pluck(_.filter($scope.fields, function (field) {
return field.display;
}), 'name');
var fields = _($scope.fields).filter('display').pluck('name').value();
// Make sure there are no columns added that aren't in the displayed field list.
$state.columns = _.intersection($state.columns, fields);
// If no columns remain, use _source
if (!$state.columns.length) {
$scope.toggleField('_source');
return;
}
// if this commit results in something besides the columns changing, a fetch will be executed.
$state.save();
if (init.complete) {
$state.save();
}
}
// TODO: Move to utility class
@ -585,7 +557,7 @@ define(function (require) {
if (!$scope.opts.timefield || $scope.vis) return Promise.resolve($scope.vis);
// TODO: a legit way to update the index pattern
$scope.vis = new Vis($scope.searchSource.get('index'), {
$scope.vis = new Vis($scope.indexPattern, {
type: 'histogram',
params: {
addLegend: false,
@ -634,6 +606,27 @@ define(function (require) {
return loadingVis;
};
function resolveIndexPatternLoading() {
var props = $route.current.locals.ip;
var loaded = props.loaded;
var stateVal = props.stateVal;
var stateValFound = props.stateValFound;
var own = $scope.searchSource.getOwn('index');
if (own && !stateVal) return own;
if (stateVal && !stateValFound) {
var err = '"' + stateVal + '" is not a configured pattern. ';
if (own) {
notify.warning(err + ' Using the saved index pattern: "' + own.id + '"');
return own;
}
notify.warning(err + ' Using the default index pattern: "' + loaded.id + '"');
}
return loaded;
}
init();
});
});