Merge branch 'master' of github.com:elastic/kibana into fix/3410/tempFieldsInDiscover

This commit is contained in:
Spencer Alger 2015-05-07 14:22:23 -07:00
commit 689b644346
65 changed files with 2463 additions and 1026 deletions

View file

@ -300,7 +300,7 @@ define(function (require) {
* @returns {object}
*/
var cleanFilter = function (filter) {
return _.omit(filter, ['$$hashKey', 'meta']);
return _.omit(filter, ['meta']);
};
// switch to filtered query if there are filters

View file

@ -1,9 +1,9 @@
define(function (require) {
return function CourierFetchRequestStatus() {
return {
ABORTED: {},
DUPLICATE: {},
INCOMPLETE: {}
ABORTED: { CourierFetchRequestStatus: 'aborted' },
DUPLICATE: { CourierFetchRequestStatus: 'duplicate' },
INCOMPLETE: { CourierFetchRequestStatus: 'incomplete' }
};
};
});

View file

@ -241,6 +241,7 @@ define(function (require) {
return self.id;
});
};
return docSource.doCreate(source)
.then(finish)
.catch(function (err) {

View file

@ -12,21 +12,25 @@
<div class="bar" ng-show="filters.length">
<div class="filter" ng-class="{ negate: filter.meta.negate, disabled: filter.meta.disabled }" ng-repeat="filter in filters">
<div class="filter-description">
<!--<span><i class="fa" ng-class="{'fa-plus': !filter.meta.negate, 'fa-minus': filter.meta.negate}"></i></span>-->
<span ng-if="filter.$state.store == 'globalState'"><i class="fa fa-fw fa-thumb-tack pinned"></i></span>
<span>{{ filter.meta.key }}:</span>
<span>"{{ filter.meta.value }}"</span>
</div>
<div class="filter-actions">
<a class="action filter-toggle" ng-click="toggleFilter(filter)">
<i ng-show="filter.meta.disabled" class="fa fa-square-o"></i>
<i ng-hide="filter.meta.disabled" class="fa fa-check-square-o"></i>
<i ng-show="filter.meta.disabled" class="fa fa-fw fa-square-o disabled"></i>
<i ng-hide="filter.meta.disabled" class="fa fa-fw fa-check-square-o enabled"></i>
</a>
<a class="action filter-pin" ng-click="pinFilter(filter)">
<i ng-show="filter.$state.store == 'globalState'" class="fa fa-fw fa-thumb-tack pinned"></i>
<i ng-hide="filter.$state.store == 'globalState'" class="fa fa-fw fa-thumb-tack fa-rotate-270 unpinned"></i>
</a>
<a class="action filter-invert" ng-click="invertFilter(filter)">
<i ng-show="filter.meta.negate" class="fa fa-search-plus"></i>
<i ng-hide="filter.meta.negate" class="fa fa-search-minus"></i>
<i ng-show="filter.meta.negate" class="fa fa-fw fa-search-plus negative"></i>
<i ng-hide="filter.meta.negate" class="fa fa-fw fa-search-minus positive"></i>
</a>
<a class="action filter-remove" ng-click="removeFilter(filter)">
<i class="fa fa-trash"></i>
<i class="fa fa-fw fa-trash"></i>
</a>
</div>
</div>
@ -52,6 +56,12 @@
<div class="filter-link">
<div class="filter-description"><a ng-click="toggleAll(true)">Disable</a></div>
</div>
<div class="filter-link">
<div class="filter-description"><a ng-click="pinAll(true)">Pin</a></div>
</div>
<div class="filter-link">
<div class="filter-description"><a ng-click="pinAll(false)">Unpin</a></div>
</div>
<div class="filter-link">
<div class="filter-description"><a ng-click="invertAll()">Invert</a></div>
</div>

View file

@ -4,34 +4,43 @@ define(function (require) {
var template = require('text!components/filter_bar/filter_bar.html');
var moment = require('moment');
var toggleFilter = require('components/filter_bar/lib/toggleFilter');
var toggleAll = require('components/filter_bar/lib/toggleAll');
var invertFilter = require('components/filter_bar/lib/invertFilter');
var invertAll = require('components/filter_bar/lib/invertAll');
var removeFilter = require('components/filter_bar/lib/removeFilter');
var removeAll = require('components/filter_bar/lib/removeAll');
var filterAppliedAndUnwrap = require('components/filter_bar/lib/filterAppliedAndUnwrap');
module.directive('filterBar', function (Private, Promise) {
module.directive('filterBar', function (Private, Promise, getAppState) {
var mapAndFlattenFilters = Private(require('components/filter_bar/lib/mapAndFlattenFilters'));
var mapFlattenAndWrapFilters = Private(require('components/filter_bar/lib/mapFlattenAndWrapFilters'));
var extractTimeFilter = Private(require('components/filter_bar/lib/extractTimeFilter'));
var filterOutTimeBasedFilter = Private(require('components/filter_bar/lib/filterOutTimeBasedFilter'));
var filterAppliedAndUnwrap = require('components/filter_bar/lib/filterAppliedAndUnwrap');
var changeTimeFilter = Private(require('components/filter_bar/lib/changeTimeFilter'));
var queryFilter = Private(require('components/filter_bar/query_filter'));
return {
restrict: 'E',
template: template,
scope: {
state: '='
},
scope: {},
link: function ($scope, $el, attrs) {
// bind query filter actions to the scope
[
'addFilters',
'toggleFilter',
'toggleAll',
'pinFilter',
'pinAll',
'invertFilter',
'invertAll',
'removeFilter',
'removeAll'
].forEach(function (method) {
$scope[method] = queryFilter[method];
});
$scope.state = getAppState();
$scope.applyFilters = function (filters) {
var newFilters = filterAppliedAndUnwrap(filters);
$scope.state.filters = _.union($scope.state.filters, newFilters);
// add new filters
$scope.addFilters(filterAppliedAndUnwrap(filters));
$scope.newFilters = [];
// change time filter
if ($scope.changeTimeFilter && $scope.changeTimeFilter.meta && $scope.changeTimeFilter.meta.apply) {
changeTimeFilter($scope.changeTimeFilter);
}
@ -42,10 +51,20 @@ define(function (require) {
$scope.changeTimeFilter = null;
};
// update the scope filter list on filter changes
$scope.$listen(queryFilter, 'update', function () {
updateFilters();
});
// when appState changes, update scope's state
$scope.$watch(getAppState, function (appState) {
$scope.state = appState;
});
$scope.$watch('state.$newFilters', function (filters) {
if (!filters) return;
// If the filters is not undefined and the length is greater then
// If filters is not undefined and the length is greater than
// one we need to set the newFilters attribute and allow the
// users to decide what they want to apply.
if (filters.length > 1) {
@ -72,24 +91,22 @@ define(function (require) {
return filters;
})
.then(filterOutTimeBasedFilter)
.then(function (filters) {
$scope.state.filters = _.union($scope.state.filters, filters);
});
.then($scope.addFilters);
}
});
$scope.$watch('state.filters', function (filters) {
function updateFilters() {
var filters = queryFilter.getFilters();
mapAndFlattenFilters(filters).then(function (results) {
$scope.filters = results;
// used to display the current filters in the state
$scope.filters = _.sortBy(results, function (filter) {
return !filter.meta.pinned;
});
$scope.$emit('filterbar:updated');
});
});
}
$scope.toggleFilter = toggleFilter($scope);
$scope.toggleAll = toggleAll($scope);
$scope.invertFilter = invertFilter($scope);
$scope.invertAll = invertAll($scope);
$scope.removeFilter = removeFilter($scope);
$scope.removeAll = removeAll($scope);
updateFilters();
}
};
});

View file

@ -99,8 +99,8 @@ filter-bar .bar {
}
> .filter-actions {
font-size: 1.3em;
line-height: 1.1em;
font-size: 1.1em;
line-height: 1.4em;
position: absolute;
padding: 4px 8px;
top: 0;
@ -109,18 +109,22 @@ filter-bar .bar {
display: none;
text-align: center;
white-space: nowrap;
}
> .filter-actions > .action {
border-right: 1px solid rgba(255, 255, 255, 0.4);
padding-right: 6px;
margin-right: 8px;
}
> * {
border-right: 1px solid rgba(255, 255, 255, 0.4);
padding-right: 0;
margin-right: 5px;
> .filter-actions > .action:last-child {
border-right: 0;
padding-right: 0;
margin-right: 0;
&:last-child {
border-right: 0;
padding-right: 0;
margin-right: 0;
}
.unpinned {
.opacity(.7);
}
}
}
&.negate {

View file

@ -29,8 +29,7 @@ define(function (require) {
if (!filters.length) return;
filters = uniqFilters(filters);
filters = dedupFilters($state.filters, filters);
filters = dedupFilters($state.filters, uniqFilters(filters));
// We need to add a bunch of filter deduping here.
$state.$newFilters = filters;
}

View file

@ -0,0 +1,33 @@
define(function (require) {
var _ = require('lodash');
var angular = require('angular');
var excludedAttributes;
var comparators;
/**
* Compare two filters to see if they match
* @param {object} first The first filter to compare
* @param {object} second The second filter to compare
* @param {object} comparatorOptions Parameters to use for comparison
* @returns {bool} Filters are the same
*/
return function (first, second, comparatorOptions) {
excludedAttributes = ['$$hashKey', 'meta'];
comparators = _.defaults(comparatorOptions || {}, {
state: false,
negate: false,
disabled: false,
});
if (!comparators.state) excludedAttributes.push('$state');
return _.isEqual(mapFilter(first), mapFilter(second));
};
function mapFilter(filter) {
var cleaned = _.omit(filter, excludedAttributes);
if (comparators.negate) cleaned.negate = filter.meta && !!filter.meta.negate;
if (comparators.disabled) cleaned.disabled = filter.meta && !!filter.meta.disabled;
return cleaned;
}
});

View file

@ -1,12 +1,22 @@
define(function (require) {
var _ = require('lodash');
var excludedAttributes = ['meta', '$$hashKey'];
return function (existing, filters) {
filters = _.filter(filters, function (item) {
return !_.find(existing, function (existingFilter) {
return _.isEqual(_.omit(existingFilter, excludedAttributes), _.omit(item, excludedAttributes));
var angular = require('angular');
var compareFilters = require('components/filter_bar/lib/compareFilters');
/**
* Combine 2 filter collections, removing duplicates
* @param {object} existing The filters to compare to
* @param {object} filters The filters being added
* @param {object} comparatorOptions Parameters to use for comparison
* @returns {object} An array of filters that were not in existing
*/
return function (existingFilters, filters, comparatorOptions) {
if (!_.isArray(filters)) filters = [filters];
return _.filter(filters, function (filter) {
return !_.find(existingFilters, function (existingFilter) {
return compareFilters(existingFilter, filter, comparatorOptions);
});
});
return filters;
};
});

View file

@ -1,17 +0,0 @@
define(function (require) {
return function ($scope) {
var invertFilter = require('components/filter_bar/lib/invertFilter')($scope);
/**
* Removes all filters
* @returns {void}
*/
return function () {
$scope.filters.forEach(function (filter) {
invertFilter(filter);
});
$scope.state.filters = $scope.filters;
};
};
});

View file

@ -1,19 +0,0 @@
define(function (require) {
var _ = require('lodash');
return function ($scope) {
/**
* Inverts the nagate value on the filter
* @param {object} filter The filter to toggle
& @param {boolean} force disabled true/false
* @returns {void}
*/
return function (filter) {
// Toggle the negate meta state
filter.meta.negate = !filter.meta.negate;
// Save the filters back to the searchSource
$scope.state.filters = $scope.filters;
return filter;
};
};
});

View file

@ -5,8 +5,13 @@ define(function (require) {
return _.deepGet(filter, 'meta.disabled');
};
return function (newFitlers, oldFilters) {
var diff = _.difference(oldFilters, newFitlers);
return (diff.length && _.every(diff, pluckDisabled));
/**
* Checks to see if only disabled filters have been changed
* @returns {bool} Only disabled filters
*/
return function (newFilters, oldFilters) {
return _.every(newFilters.concat(oldFilters), function (newFilter) {
return pluckDisabled(newFilter);
});
};
});

View file

@ -0,0 +1,19 @@
define(function (require) {
var _ = require('lodash');
var makeComparable = function (filter) {
return _.omit(filter, ['$state', '$$hashKey']);
};
/**
* Checks to see if only disabled filters have been changed
* @returns {bool} Only disabled filters
*/
return function (newFilters, oldFilters) {
var comparableOldFilters = _.map(oldFilters, makeComparable);
return _.every(newFilters, function (newFilter, i) {
var match = _.find(comparableOldFilters, makeComparable(newFilter));
return !!match;
});
};
});

View file

@ -1,11 +0,0 @@
define(function (require) {
return function ($scope) {
/**
* Removes all filters
* @returns {void}
*/
return function () {
$scope.state.filters = [];
};
};
});

View file

@ -1,20 +0,0 @@
define(function (require) {
var _ = require('lodash');
return function ($scope) {
/**
* Removes the filter from the searchSource
* @param {object} filter The filter to remove
* @returns {void}
*/
return function (invalidFilter) {
// Remove the filter from the the scope $filters and map it back
// to the original format to save in searchSource
$scope.state.filters = _($scope.filters)
.filter(function (filter) {
return filter !== invalidFilter;
})
.value();
};
};
});

View file

@ -1,19 +0,0 @@
define(function (require) {
var _ = require('lodash');
return function ($scope) {
var toggleFilter = require('components/filter_bar/lib/toggleFilter')($scope);
/**
* Disables all filters
* @params {boolean} force disable/enable all filters
* @returns {void}
*/
return function (force) {
$scope.filters.forEach(function (filter) {
toggleFilter(filter, force);
});
$scope.state.filters = $scope.filters;
};
};
});

View file

@ -1,20 +0,0 @@
define(function (require) {
var _ = require('lodash');
return function ($scope) {
/**
* Toggles the filter between enabled/disabled.
* @param {object} filter The filter to toggle
& @param {boolean} force disabled true/false
* @returns {void}
*/
return function (filter, force) {
// Toggle the disabled flag
var disabled = _.isUndefined(force) ? !filter.meta.disabled : force;
filter.meta.disabled = disabled;
// Save the filters back to the searchSource
$scope.state.filters = $scope.filters;
return filter;
};
};
});

View file

@ -1,10 +1,16 @@
define(function (require) {
var _ = require('lodash');
var dedupFilters = require('components/filter_bar/lib/dedupFilters');
return function (filters) {
/**
* Remove duplicate filters from an array of filters
* @param {array} filters The filters to remove duplicates from
* @returns {object} The original filters array with duplicates removed
*/
return function (filters, comparatorOptions) {
var results = [];
_.each(filters, function (filter) {
results = _.union(results, dedupFilters(results, [filter]));
results = _.union(results, dedupFilters(results, [filter], comparatorOptions));
});
return results;
};

View file

@ -1,24 +0,0 @@
define(function (require) {
return function watchFiltersProvider(Private, Promise, Notifier) {
var _ = require('lodash');
var onlyDisabled = require('components/filter_bar/lib/onlyDisabled');
var EventEmitter = Private(require('factories/events'));
var notify = new Notifier({ location: 'Fitler Bar' });
return function ($scope, handlers) {
var emitter = new EventEmitter();
$scope.$watch('state.filters', function (newFilters, oldFilters) {
if (newFilters === oldFilters) return;
return emitter.emit('update')
.then(function () {
if (onlyDisabled(newFilters, oldFilters)) return;
return emitter.emit('fetch');
});
});
return emitter;
};
};
});

View file

@ -0,0 +1,329 @@
define(function (require) {
var _ = require('lodash');
return function (Private, $rootScope, getAppState, globalState) {
var EventEmitter = Private(require('factories/events'));
var onlyDisabled = require('components/filter_bar/lib/onlyDisabled');
var onlyStateChanged = require('components/filter_bar/lib/onlyStateChanged');
var uniqFilters = require('components/filter_bar/lib/uniqFilters');
var compareFilters = require('components/filter_bar/lib/compareFilters');
var queryFilter = new EventEmitter();
queryFilter.getFilters = function () {
var compareOptions = { disabled: true, negate: true };
var appFilters = queryFilter.getAppFilters();
var globalFilters = queryFilter.getGlobalFilters();
return uniqFilters(globalFilters.concat(appFilters), compareOptions);
};
queryFilter.getAppFilters = function () {
var appState = getAppState();
if (!appState || !appState.filters) return [];
return (appState.filters) ? _.map(appState.filters, appendStoreType('appState')) : [];
};
queryFilter.getGlobalFilters = function () {
if (!globalState.filters) return [];
return _.map(globalState.filters, appendStoreType('globalState'));
};
/**
* Adds new filters to the scope and state
* @param {object|array} fitlers Filter(s) to add
* @param {bool} global Should be added to global state
* @returns {object} Resulting new filter list
*/
queryFilter.addFilters = function (filters, global) {
var appState = getAppState();
var state = (global) ? globalState : appState;
if (!_.isArray(filters)) {
filters = [filters];
}
if (global) {
// simply concat global filters, they will be deduped
globalState.filters = globalState.filters.concat(filters);
} else if (appState) {
if (!appState.filters) appState.filters = [];
var mergeOptions = { disabled: true, negate: false };
var appFilters = appState.filters.concat(filters);
var merged = mergeAndMutateFilters(globalState.filters, appFilters, mergeOptions);
globalState.filters = merged[0];
appState.filters = merged[1];
}
return saveState();
};
/**
* Removes the filter from the proper state
* @param {object} matchFilter The filter to remove
* @returns {object} Resulting new filter list
*/
queryFilter.removeFilter = function (matchFilter) {
var state = getStateByFilter(matchFilter);
if (!state) return;
_.pull(state.filters, matchFilter);
return saveState();
};
/**
* Removes all filters
* @returns {object} Resulting new filter list
*/
queryFilter.removeAll = function () {
var appState = getAppState();
appState.filters = [];
globalState.filters = [];
return saveState();
};
/**
* Toggles the filter between enabled/disabled.
* @param {object} filter The filter to toggle
& @param {boolean} force Disabled true/false
* @returns {object} updated filter
*/
queryFilter.toggleFilter = function (filter, force) {
// Toggle the disabled flag
var disabled = _.isUndefined(force) ? !filter.meta.disabled : !!force;
filter.meta.disabled = disabled;
// Save the filters back to the searchSource
saveState();
return filter;
};
/**
* Disables all filters
* @params {boolean} force Disable/enable all filters
* @returns {object} Resulting updated filter list
*/
queryFilter.toggleAll = function (force) {
function doToggle(filter) {
queryFilter.toggleFilter(filter, force);
}
executeOnFilters(doToggle);
return queryFilter.getFilters();
};
/**
* Inverts the nagate value on the filter
* @param {object} filter The filter to toggle
* @returns {object} updated filter
*/
queryFilter.invertFilter = function (filter) {
// Toggle the negate meta state
filter.meta.negate = !filter.meta.negate;
saveState();
return filter;
};
/**
* Inverts all filters
* @returns {object} Resulting updated filter list
*/
queryFilter.invertAll = function () {
executeOnFilters(queryFilter.invertFilter);
return queryFilter.getFilters();
};
/**
* Pins the filter to the global state
* @param {object} filter The filter to pin
* @param {boolean} force pinned state
* @returns {object} filter passed in
*/
queryFilter.pinFilter = function (filter, force) {
var appState = getAppState();
if (!appState) return filter;
// ensure that both states have a filters property
if (!_.isArray(globalState.filters)) globalState.filters = [];
if (!_.isArray(appState.filters)) appState.filters = [];
var appIndex = _.indexOf(appState.filters, filter);
var globalIndex = _.indexOf(globalState.filters, filter);
if (appIndex === -1 && globalIndex === -1) return;
if (appIndex !== -1 && force !== false) {
appState.filters.splice(appIndex, 1);
globalState.filters.push(filter);
} else if (globalIndex !== -1 && force !== true) {
globalState.filters.splice(globalIndex, 1);
appState.filters.push(filter);
}
saveState();
return filter;
};
/**
* Pins all filters
* @params {boolean} force Pin/Unpin all filters
* @returns {object} Resulting updated filter list
*/
queryFilter.pinAll = function (force) {
function pin(filter) {
queryFilter.pinFilter(filter, force);
}
executeOnFilters(pin);
return queryFilter.getFilters();
};
initWatchers();
return queryFilter;
/**
* Saves both app and global states, ensuring filters are persisted
* @returns {object} Resulting filter list, app and global combined
*/
function saveState() {
var appState = getAppState();
if (appState) appState.save();
globalState.save();
return queryFilter.getFilters();
}
function appendStoreType(type) {
return function (filter) {
filter.$state = {
store: type
};
return filter;
};
}
// get state (app or global) or the filter passed in
function getStateByFilter(filter) {
var appState = getAppState();
if (appState) {
var appIndex = _.indexOf(appState.filters, filter);
if (appIndex !== -1) return appState;
}
var globalIndex = _.indexOf(globalState.filters, filter);
if (globalIndex !== -1) return globalState;
return false;
}
// helper to run a function on all filters in all states
function executeOnFilters(fn) {
var appState = getAppState();
var appFilters;
if (appState && appState.filters) {
appFilters = appState.filters;
} else {
appFilters = [];
}
globalState.filters.concat(appFilters).forEach(fn);
}
function mergeAndMutateFilters(globalFilters, appFilters, compareOptions) {
appFilters = appFilters || [];
globalFilters = globalFilters || [];
compareOptions = _.defaults(compareOptions || {}, { disabled: true, negate: true });
// existing globalFilters should be mutated by appFilters
appFilters = _.filter(appFilters, function (filter) {
var match = _.find(globalFilters, function (globalFilter) {
return compareFilters(globalFilter, filter, compareOptions);
});
// if the filter remains, it doesn't match any filters in global state
if (!match) return true;
// filter matches a filter in globalFilters, mutate existing global filter
_.assign(match.meta, filter.meta);
return false;
});
appFilters = uniqFilters(appFilters, { disabled: true });
globalFilters = uniqFilters(globalFilters, { disabled: true });
return [globalFilters, appFilters];
}
/**
* Initializes state watchers that use the event emitter
* @returns {void}
*/
function initWatchers() {
var removeAppStateWatchers;
$rootScope.$watch(getAppState, function () {
removeAppStateWatchers && removeAppStateWatchers();
removeAppStateWatchers = initAppStateWatchers();
});
function initAppStateWatchers() {
// multi watch on the app and global states
var stateWatchers = [{
fn: $rootScope.$watch,
deep: true,
get: queryFilter.getGlobalFilters
}, {
fn: $rootScope.$watch,
deep: true,
get: queryFilter.getAppFilters
}];
// when states change, use event emitter to trigger updates and fetches
return $rootScope.$watchMulti(stateWatchers, function (next, prev) {
var doUpdate = false;
var doFetch = false;
var newFilters = [];
var oldFilters = [];
// iterate over each state type, checking for changes
stateWatchers.forEach(function (watcher, i) {
var nextVal = next[i];
var prevVal = prev[i];
newFilters = newFilters.concat(nextVal);
oldFilters = oldFilters.concat(prevVal);
// no update or fetch if there was no change
if (nextVal === prevVal) return;
if (nextVal) doUpdate = true;
// don't trigger fetch when only disabled filters
if (!onlyDisabled(nextVal, prevVal)) doFetch = true;
});
// make sure change wasn't only a state move
if (doFetch && newFilters.length === oldFilters.length) {
if (onlyStateChanged(newFilters, oldFilters)) doFetch = false;
}
// reconcile filter in global and app states
var filters = mergeAndMutateFilters(next[0], next[1]);
globalState.filters = filters[0];
var appState = getAppState();
if (appState) {
appState.filters = filters[1];
}
saveState();
if (!doUpdate) return;
return queryFilter.emit('update')
.then(function () {
if (!doFetch) return;
return queryFilter.emit('fetch');
});
});
}
}
};
});

View file

@ -1,73 +1,79 @@
// Adds a filter to a passed state
define(function (require) {
var _ = require('lodash');
var self = this;
return function (Private) {
var _ = require('lodash');
var queryFilter = Private(require('components/filter_bar/query_filter'));
var filterManager = {};
this.init = function ($state) {
self.$state = $state;
};
filterManager.add = function (field, values, operation, index) {
values = _.isArray(values) ? values : [values];
var fieldName = _.isObject(field) ? field.name : field;
var filters = _.flatten([queryFilter.getAppFilters()], true);
var newFilters = [];
this.add = function (field, values, operation, index) {
var negate = (operation === '-');
values = _.isArray(values) ? values : [values];
// Have we been passed a simple name or an actual field object?
var fieldName = _.isObject(field) ? field.name : field;
var negate = operation === '-';
var filters = _.flatten([self.$state.filters], true);
// TODO: On array fields, negating does not negate the combination, rather all terms
_.each(values, function (value) {
var existing = _.find(filters, function (filter) {
if (!filter) return;
if (fieldName === '_exists_' && filter.exists) {
return filter.exists.field === value;
}
if (filter.query) {
return filter.query.match[fieldName] && filter.query.match[fieldName].query === value;
}
});
if (existing) {
if (existing.meta.negate !== negate) {
existing.meta.negate = negate;
}
return;
}
switch (fieldName) {
case '_exists_':
filters.push({ meta: { negate: negate, index: index }, exists: { field: value } });
break;
default:
// TODO: On array fields, negating does not negate the combination, rather all terms
_.each(values, function (value) {
var filter;
if (field.scripted) {
var existing = _.find(filters, function (filter) {
if (!filter) return;
if (fieldName === '_exists_' && filter.exists) {
return filter.exists.field === value;
}
if (filter.query) {
return filter.query.match[fieldName] && filter.query.match[fieldName].query === value;
}
});
if (existing) {
existing.meta.disabled = false;
if (existing.meta.negate !== negate) {
queryFilter.invertFilter(existing);
}
return;
}
switch (fieldName) {
case '_exists_':
filter = {
meta: { negate: negate, index: index, field: fieldName },
script: {
script: '(' + field.script + ') == value',
lang: field.lang,
params: {
value: value
}
meta: {
negate: negate,
index: index
},
exists: {
field: value
}
};
} else {
filter = { meta: { negate: negate, index: index }, query: { match: {} } };
filter.query.match[fieldName] = { query: value, type: 'phrase' };
break;
default:
if (field.scripted) {
filter = {
meta: { negate: negate, index: index, field: fieldName },
script: {
script: '(' + field.script + ') == value',
lang: field.lang,
params: {
value: value
}
}
};
} else {
filter = { meta: { negate: negate, index: index }, query: { match: {} } };
filter.query.match[fieldName] = { query: value, type: 'phrase' };
}
break;
}
filters.push(filter);
break;
}
});
newFilters.push(filter);
});
self.$state.filters = filters;
queryFilter.addFilters(newFilters);
};
return filterManager;
};
return this;
});

View file

@ -58,8 +58,7 @@ define(function (require) {
State.prototype.fetch = function () {
var stash = this._readFromURL();
// nothing to read from the url?
// we should save if were are ordered to persist
// nothing to read from the url? save if ordered to persist
if (stash === null) {
if (this._persistAcrossApps) {
return this.save();

View file

@ -8,19 +8,18 @@
</div>
<div class="form-group">
<span class="pull-right text-info hintbox-label" ng-click="editor.showUrlTemplateHelp = !editor.showUrlTemplateHelp">
<span class="pull-right text-info hintbox-label" ng-click="editor.showUrlTmplHelp = !editor.showUrlTmplHelp">
<i class="fa fa-info"></i> Url Template Help
</span>
<label>Template</label>
<text ng-model="editor.formatParams.format">
<div class="hintbox" ng-if="editor.showUrlTemplateHelp">
<label>Url Template</label>
<div class="hintbox" ng-if="editor.showUrlTmplHelp">
<h4 class="hintbox-heading">
<i class="fa fa-question-circle text-info"></i> Url Template Help
</h4>
<p>
If a field only contains part of a url then a "Url Template" can be used to format the value as a complete url. The format is a string which uses double curly brace notation <code ng-bind="'\{\{ \}\}'"></code> to inject values. The following values can be accessed:
If a field only contains part of a url then a "Url Template" can be used to format the value as a complete url. The format is a string which uses double curly brace notation <code>{&shy;{ }&shy;}</code> to inject values. The following values can be accessed:
</p>
<ul>
@ -31,10 +30,95 @@
<strong>rawValue</strong> &mdash; The unescaped value
</li>
</ul>
<table class="table table-striped table-bordered">
<caption>Examples</caption>
<thead>
<tr>
<th>Value</th>
<th>Template</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>1234</td>
<td>http://company.net/profiles?user_id={&shy;{value}&shy;}</td>
<td>http://company.net/profiles?user_id=1234</td>
</tr>
<tr>
<td>users/admin</td>
<td>http://company.net/groups?id={&shy;{value}&shy;}</td>
<td>http://company.net/groups?id=users%2Fadmin</td>
</tr>
<tr>
<td>/images/favicon.ico</td>
<td>http://www.site.com{&shy;{rawValue}&shy;}</td>
<td>http://www.site.com/images/favicon.ico</td>
</tr>
</tbody>
</table>
</div>
<field-format-editor-pattern
ng-model="editor.formatParams.template"
inputs="url.sampleInputs">
</field-format-editor-pattern>
<input ng-model="editor.formatParams.urlTemplate" class="form-control">
</div>
<div class="form-group">
<span class="pull-right text-info hintbox-label" ng-click="editor.showLabelTmplHelp = !editor.showLabelTmplHelp">
<i class="fa fa-info"></i> Label Template Help
</span>
<label>Label Template</label>
<div class="hintbox" ng-if="editor.showLabelTmplHelp">
<h4 class="hintbox-heading">
<i class="fa fa-question-circle text-info"></i> Label Template Help
</h4>
<p>
If the url in this field is large, it might be useful to provide an alternate template for the text version of the url. This will be displayed instead of the url, but will still link to the url. The format is a string which uses double curly brace notation <code>{&shy;{ }&shy;}</code> to inject values. The following values can be accessed:
</p>
<ul>
<li>
<strong>value</strong> &mdash; The fields value
</li>
<li>
<strong>url</strong> &mdash; The formatted url
</li>
</ul>
<table class="table table-striped table-bordered">
<caption>Examples</caption>
<thead>
<tr>
<th>Value</th>
<th>Url Template</th>
<th>Label Template</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>1234</td>
<td>http://company.net/profiles?user_id={&shy;{value}&shy;}</td>
<td>User #{&shy;{value}&shy;}</td>
<td>
<a href="http://company.net/profiles?user_id=1234">User #1234</a>
</td>
</tr>
<tr>
<td>/assets/main.css</td>
<td>http://site.com{&shy;{rawValue}&shy;}</td>
<td>View Asset</td>
<td>
<a href="http://site.com/assets/main.css">View Asset</a>
</td>
</tr>
</tbody>
</table>
</div>
<input ng-model="editor.formatParams.labelTemplate" class="form-control">
</div>
<field-format-editor-samples inputs="url.sampleInputs"></field-format-editor-samples>

View file

@ -35,7 +35,8 @@ define(function (require) {
Url.templateMatchRE = /{{([\s\S]+?)}}/g;
Url.paramDefaults = {
type: 'a',
template: null
urlTemplate: null,
labelTemplate: null
};
Url.urlTypes = [
@ -43,25 +44,45 @@ define(function (require) {
{ id: 'img', name: 'Image' }
];
Url.prototype._formatUrl = function (value) {
var template = this.param('urlTemplate');
if (!template) return value;
return this._compileTemplate(template)({
value: encodeURIComponent(value),
rawValue: value
});
};
Url.prototype._formatLabel = function (value, url) {
var template = this.param('labelTemplate');
if (url == null) url = this._formatUrl(value);
if (!template) return url;
return this._compileTemplate(template)({
value: value,
url: url
});
};
Url.prototype._convert = {
text: function (value) {
var template = this.param('template');
return !template ? value : this._compileTemplate(template)(value);
return this._formatLabel(value);
},
html: function (rawValue, field, hit) {
var url = _.escape(this.convert(rawValue, 'text'));
var value = _.escape(rawValue);
var url = _.escape(this._formatUrl(rawValue));
var label = _.escape(this._formatLabel(rawValue, url));
switch (this.param('type')) {
case 'img': return '<img src="' + url + '" alt="' + value + '" title="' + value + '">';
case 'img':
return '<img src="' + url + '" alt="' + label + '" title="' + label + '">';
default:
var urlDisplay = url;
if (hit && hit.highlight && hit.highlight[field.name]) {
urlDisplay = highlightFilter(url, hit.highlight[field.name]);
label = highlightFilter(label, hit.highlight[field.name]);
}
return '<a href="' + url + '" target="_blank">' + urlDisplay + '</a>';
return '<a href="' + url + '" target="_blank">' + label + '</a>';
}
}
};
@ -72,12 +93,7 @@ define(function (require) {
return (i % 2) ? part.trim() : part;
});
return function (val) {
var locals = {
value: encodeURIComponent(val),
rawValue: val
};
return function (locals) {
// replace all the odd bits with their local var
var output = '';
var i = -1;

View file

@ -56,6 +56,7 @@ define(function (require) {
// create a new maps array
self.maps = [];
self.popups = [];
var worldBounds = L.latLngBounds([-90, -220], [90, 220]);
@ -158,23 +159,29 @@ define(function (require) {
self.addLabel(mapData.properties.label, map);
}
// Add button to fit container to points
var FitControl = L.Control.extend({
options: {
position: 'topleft'
},
onAdd: function (map) {
var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-zoom leaflet-control-fit');
$(container).html('<a class="leaflet-control-zoom fa fa-crop" title="Fit Data Bounds"></a>');
$(container).on('click', function () {
self.fitBounds(map, featureLayer);
});
return container;
}
});
var fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-zoom leaflet-control-fit');
if (mapData && mapData.features.length > 0) {
map.addControl(new FitControl());
// Add button to fit container to points
var FitControl = L.Control.extend({
options: {
position: 'topleft'
},
onAdd: function (map) {
$(fitContainer).html('<a class="leaflet-control-zoom fa fa-crop" title="Fit Data Bounds"></a>');
$(fitContainer).on('click', function () {
self.fitBounds(map, featureLayer);
});
return fitContainer;
},
onRemove: function (map) {
$(fitContainer).off('click');
}
});
map.fitControl = new FitControl();
map.addControl(map.fitControl);
} else {
map.fitControl = undefined;
}
self.maps.push(map);
@ -517,6 +524,8 @@ define(function (require) {
.on('mouseout', function (e) {
layer.closePopup();
});
this.popups.push({elem: popup, layer: layer});
};
/**
@ -617,16 +626,22 @@ define(function (require) {
* @return {undefined}
*/
TileMap.prototype.destroy = function () {
if (this.maps && this.maps.length) {
this.maps.forEach(function (map) {
// Cleanup hanging DOM nodes
// TODO: The correct way to handle this is to ensure all listeners are properly removed
var children = $(map._container).find('*');
map.remove();
children.remove();
if (this.popups) {
this.popups.forEach(function (popup) {
popup.elem.off('mouseover').off('mouseout');
popup.layer.unbindPopup(popup.elem);
});
this.popups = [];
}
if (this.maps) {
this.maps.forEach(function (map) {
if (map.fitControl) {
map.fitControl.removeFrom(map);
}
map.remove();
});
}
};
return TileMap;

View file

@ -41,6 +41,8 @@ define(function (require) {
var vals = new Array(expressions.length);
var prev = new Array(expressions.length);
var fire = false;
var init = 0;
var neededInits = expressions.length;
// first, register all of the multi-watchers
var unwatchers = expressions.map(function (expr, i) {
@ -48,6 +50,10 @@ define(function (require) {
if (!expr) return;
return expr.fn.call($scope, expr.get, function (newVal, oldVal) {
if (newVal === oldVal) {
init += 1;
}
vals[i] = newVal;
prev[i] = oldVal;
fire = true;
@ -58,12 +64,16 @@ define(function (require) {
// the other watchers triggered this cycle
var flip = false;
unwatchers.push($scope.$watch(function () {
if (init < neededInits) return init;
if (fire) {
fire = false;
flip = !flip;
}
return flip;
}, function () {
if (init < neededInits) return false;
fn(vals.slice(0), prev.slice(0));
vals.forEach(function (v, i) {
prev[i] = v;
@ -104,4 +114,4 @@ define(function (require) {
return $delegate;
});
});
});
});

View file

@ -1,12 +1,12 @@
define(function (require) {
var moment = require('moment');
var filterManager = require('components/filter_manager/filter_manager');
var $ = require('jquery');
require('modules')
.get('app/dashboard')
.directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $compile) {
var _ = require('lodash');
var loadPanel = Private(require('plugins/dashboard/components/panel/lib/load_panel'));
var filterManager = Private(require('components/filter_manager/filter_manager'));
var notify = new Notifier();
require('components/visualize/visualize');
@ -32,7 +32,6 @@ define(function (require) {
$scope.edit = panelConfig.edit;
$scope.$on('$destroy', panelConfig.savedObj.destroy);
filterManager.init($state);
$scope.filter = function (field, value, operator) {
var index = $scope.savedObj.searchSource.get('index').id;
filterManager.add(field, value, operator, index);

View file

@ -50,7 +50,7 @@ define(function (require) {
app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, kbnUrl) {
return {
controller: function ($scope, $route, $routeParams, $location, configFile, Private, getAppState) {
var filterBarWatchFilters = Private(require('components/filter_bar/lib/watchFilters'));
var queryFilter = Private(require('components/filter_bar/query_filter'));
var notify = new Notifier({
location: 'Dashboard'
@ -110,22 +110,24 @@ define(function (require) {
}
function updateQueryOnRootSource() {
var filters = $state.filters;
var filters = queryFilter.getFilters();
if ($state.query) {
dash.searchSource.set('filter', _.union($state.filters, [{
query: $state.query
dash.searchSource.set('filter', _.union(filters, [{
query: $state.query
}]));
} else {
dash.searchSource.set('filter', filters);
}
}
filterBarWatchFilters($scope)
.on('update', function () {
// update root source when filters update
$scope.$listen(queryFilter, 'update', function () {
updateQueryOnRootSource();
$state.save();
})
.on('fetch', $scope.refresh);
});
// update data when filters fire fetch event
$scope.$listen(queryFilter, 'fetch', $scope.refresh);
$scope.newDashboard = function () {
kbnUrl.change('/dashboard', {});

View file

@ -3,7 +3,6 @@ define(function (require) {
var angular = require('angular');
var moment = require('moment');
var ConfigTemplate = require('utils/config_template');
var filterManager = require('components/filter_manager/filter_manager');
var getSort = require('components/doc_table/lib/get_sort');
var rison = require('utils/rison');
@ -69,7 +68,8 @@ define(function (require) {
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 filterBarWatchFilters = Private(require('components/filter_bar/lib/watchFilters'));
var queryFilter = Private(require('components/filter_bar/query_filter'));
var filterManager = Private(require('components/filter_manager/filter_manager'));
var notify = new Notifier({
location: 'Discover'
@ -115,7 +115,7 @@ define(function (require) {
columns: savedSearch.columns || ['_source'],
index: $scope.indexPattern.id,
interval: 'auto',
filters: _.cloneDeep($scope.searchSource.get('filter'))
filters: _.cloneDeep($scope.searchSource.getOwn('filter'))
};
}
@ -126,8 +126,6 @@ define(function (require) {
$state.save();
});
filterManager.init($state);
$scope.opts = {
// number of records to fetch, then paginate through
sampleSize: config.get('discover:sampleSize'),
@ -164,13 +162,15 @@ define(function (require) {
if (!angular.equals(sort, currentSort)) $scope.fetch();
});
filterBarWatchFilters($scope)
.on('update', function () {
// update data source when filters update
$scope.$listen(queryFilter, 'update', function () {
return $scope.updateDataSource().then(function () {
$state.save();
});
})
.on('fetch', $scope.fetch);
});
// fetch data when filters fire fetch event
$scope.$listen(queryFilter, 'fetch', $scope.fetch);
$scope.$watch('opts.timefield', function (timefield) {
timefilter.enabled = !!timefield;
@ -423,7 +423,7 @@ define(function (require) {
fields: {'*': {}},
fragment_size: 2147483647 // Limit of an integer.
})
.set('filter', $state.filters || []);
.set('filter', queryFilter.getFilters());
});
// TODO: On array fields, negating does not negate the combination, rather all terms

View file

@ -79,6 +79,20 @@ define(function (require) {
// build collection of agg params html
type.params.forEach(function (param, i) {
var aggParam;
// if field param exists, compute allowed fields
if (param.name === 'field') {
$aggParamEditorsScope.indexedFields = getIndexedFields(param);
}
if ($aggParamEditorsScope.indexedFields) {
var hasIndexedFields = $aggParamEditorsScope.indexedFields.length > 0;
var isExtraParam = i > 0;
if (!hasIndexedFields && isExtraParam) { // don't draw the rest of the options if their are no indexed fields.
return;
}
}
var type = 'basic';
if (param.advanced) type = 'advanced';
@ -86,10 +100,6 @@ define(function (require) {
aggParamHTML[type].push(aggParam);
}
// if field param exists, compute allowed fields
if (param.name === 'field') {
$aggParamEditorsScope.indexedFields = getIndexedFields(param);
}
});
// compile the paramEditors html elements
@ -112,8 +122,7 @@ define(function (require) {
}
var attrs = {
'agg-param': 'agg.type.params[' + idx + ']',
'ng-hide': '!indexedFields.length && ' + idx + ' > 0' // if there are no fields, and this is one of the extra options
'agg-param': 'agg.type.params[' + idx + ']'
};
if (param.advanced) {

View file

@ -58,7 +58,7 @@ define(function (require) {
var Notifier = require('components/notify/_notifier');
var docTitle = Private(require('components/doc_title/doc_title'));
var brushEvent = Private(require('utils/brush_event'));
var filterBarWatchFilters = Private(require('components/filter_bar/lib/watchFilters'));
var queryFilter = Private(require('components/filter_bar/query_filter'));
var filterBarClickHandler = Private(require('components/filter_bar/filter_bar_click_handler'));
var notify = new Notifier({
@ -152,19 +152,16 @@ define(function (require) {
timefilter.enabled = !!timeField;
});
filterBarWatchFilters($scope)
.on('update', function () {
if ($state.filters && $state.filters.length) {
searchSource.set('filter', $state.filters);
} else {
searchSource.set('filter', []);
}
// update the searchSource when filters update
$scope.$listen(queryFilter, 'update', function () {
searchSource.set('filter', queryFilter.getFilters());
$state.save();
})
.on('fetch', function () {
$scope.fetch();
});
// fetch data when filters fire fetch event
$scope.$listen(queryFilter, 'fetch', $scope.fetch);
$scope.$listen($state, 'fetch_with_changes', function (keys) {
if (_.contains(keys, 'linked') && $state.linked === true) {
// abort and reload route
@ -187,7 +184,7 @@ define(function (require) {
}
if (_.isEqual(keys, ['filters'])) {
// updates will happen in filterBarWatchFilters() if needed
// updates will happen in filter watcher if needed
return;
}
@ -210,7 +207,7 @@ define(function (require) {
$scope.fetch = function () {
$state.save();
searchSource.set('filter', $state.filters);
searchSource.set('filter', queryFilter.getFilters());
if (!$state.linked) searchSource.set('query', $state.query);
if ($scope.vis.type.requiresSearch) {
courier.fetch();

View file

@ -1,6 +1,7 @@
@hintbox-background-color: @gray-lighter;
@hintbox-spacing-vertical: 10px;
@hintbox-spacing-horizontal: 12px;
@hintbox-table-border-color: #BFC9CA;
.hintbox {
padding: @hintbox-spacing-vertical @hintbox-spacing-horizontal;
@ -38,4 +39,25 @@
> * + * {
margin-top: @hintbox-spacing-vertical;
}
// https://github.com/twbs/bootstrap/blob/2aa102bfd40859d15790febed1939e0111a6fb1a/less/tables.less#L88-L106
.table-bordered {
border: 1px solid @hintbox-table-border-color;
> thead,
> tbody,
> tfoot {
> tr {
> th,
> td {
border: 1px solid @hintbox-table-border-color;
}
}
}
> thead > tr {
> th,
> td {
border-bottom-width: 2px;
}
}
}
}

View file

@ -295,5 +295,19 @@ define(function (require) {
return list;
},
pushAll: function (source, dest) {
var start = dest.length;
var adding = source.length;
// allocate - http://goo.gl/e2i0S0
dest.length = start + adding;
// fill sparse positions
var i = -1;
while (++i < adding) dest[start + i] = source[i];
return dest;
}
};
});

View file

@ -48,7 +48,7 @@ module.exports = function (app) {
return path.basename(filename).charAt(0) !== '_';
})
.map(function (filename) {
return path.relative(unit, filename).replace(/\.js$/, '');
return path.relative(unit, filename).replace(/\\/g, '/').replace(/\.js$/, '');
});
res.end(JSON.stringify(moduleIds));

View file

@ -6,7 +6,8 @@ var plugins = function (dir) {
if (!dir) return [];
var files = glob.sync(path.join(dir, '*', 'index.js')) || [];
return files.map(function (file) {
return file.replace(dir, 'plugins').replace(/\.js$/, '');
var relative = path.relative(dir, file);
return path.join('plugins', relative).replace(/\\/g, '/').replace(/\.js$/, '');
});
};

View file

@ -5,6 +5,7 @@ define(function (require) {
{ name: 'ssl', type: 'boolean', indexed: true, analyzed: true, sortable: true, filterable: true, count: 20 },
{ name: '@timestamp', type: 'date', indexed: true, analyzed: true, sortable: true, filterable: true, count: 30 },
{ name: 'time', type: 'date', indexed: true, analyzed: true, sortable: true, filterable: true, count: 30 },
{ name: '@tags', type: 'string', indexed: true, analyzed: true, sortable: true, filterable: true },
{ name: 'utc_time', type: 'date', indexed: true, analyzed: true, sortable: true, filterable: true },
{ name: 'phpmemory', type: 'number', indexed: true, analyzed: true, sortable: true, filterable: true },
{ name: 'ip', type: 'ip', indexed: true, analyzed: true, sortable: true, filterable: true },

View file

@ -0,0 +1,18 @@
define(function (require) {
var _ = require('lodash');
var sinon = require('test_utils/auto_release_sinon');
function MockState(defaults) {
this.on = _.noop;
this.off = _.noop;
this.save = sinon.stub();
_.assign(this, defaults);
}
MockState.prototype.resetStub = function () {
this.save = sinon.stub();
return this;
};
return MockState;
});

View file

@ -153,6 +153,7 @@ define(function (require) {
var section = getSections($elem);
$scope.columns.push('bytes');
$scope.$digest();
expect(section.selected.text()).to.contain('bytes');
expect(section.popular.text()).to.not.contain('bytes');

View file

@ -0,0 +1,130 @@
define(function (require) {
return ['add filters', function () {
var _ = require('lodash');
var sinon = require('test_utils/auto_release_sinon');
var MockState = require('fixtures/mock_state');
var storeNames = {
app: 'appState',
global: 'globalState'
};
var filters;
var queryFilter;
var $rootScope, appState, globalState;
beforeEach(module('kibana'));
beforeEach(function () {
appState = new MockState({ filters: [] });
globalState = new MockState({ filters: [] });
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 }
}
];
});
beforeEach(function () {
module('kibana/global_state', function ($provide) {
$provide.service('getAppState', function () {
return function () {
return appState;
};
});
$provide.service('globalState', function () {
return globalState;
});
});
});
beforeEach(function () {
inject(function (_$rootScope_, Private) {
$rootScope = _$rootScope_;
queryFilter = Private(require('components/filter_bar/query_filter'));
});
});
describe('adding filters', function () {
it('should add filters to appState', function () {
queryFilter.addFilters(filters);
expect(appState.filters.length).to.be(3);
expect(globalState.filters.length).to.be(0);
});
it('should add filters to globalState', function () {
queryFilter.addFilters(filters, true);
expect(appState.filters.length).to.be(0);
expect(globalState.filters.length).to.be(3);
});
it('should accept a single filter', function () {
queryFilter.addFilters(filters[0]);
expect(appState.filters.length).to.be(1);
expect(globalState.filters.length).to.be(0);
});
it('should fire the update and fetch events', function () {
var emitSpy = sinon.spy(queryFilter, 'emit');
// set up the watchers
$rootScope.$digest();
queryFilter.addFilters(filters);
// trigger the digest loop to fire the watchers
$rootScope.$digest();
expect(emitSpy.callCount).to.be(2);
expect(emitSpy.firstCall.args[0]).to.be('update');
expect(emitSpy.secondCall.args[0]).to.be('fetch');
});
});
describe('filter reconciliation', function () {
it('should de-dupe appState filters being added', function () {
var newFilter = _.cloneDeep(filters[1]);
appState.filters = filters;
expect(appState.filters.length).to.be(3);
queryFilter.addFilters(newFilter);
$rootScope.$digest();
expect(appState.filters.length).to.be(3);
});
it('should de-dupe globalState filters being added', function () {
var newFilter = _.cloneDeep(filters[1]);
globalState.filters = filters;
expect(globalState.filters.length).to.be(3);
queryFilter.addFilters(newFilter, true);
$rootScope.$digest();
expect(globalState.filters.length).to.be(3);
});
it('should mutate global filters on appState filter changes', function () {
var idx = 1;
globalState.filters = filters;
var appFilter = _.cloneDeep(filters[idx]);
appFilter.meta.negate = true;
// use addFilters here, so custom adding logic can be applied
queryFilter.addFilters(appFilter);
var res = queryFilter.getFilters();
expect(res).to.have.length(3);
_.each(res, function (filter, i) {
expect(filter.$state.store).to.be('globalState');
// make sure global filter actually mutated
expect(filter.meta.negate).to.be(i === idx);
});
});
});
}];
});

View file

@ -0,0 +1,164 @@
define(function (require) {
return ['get filters', function () {
var _ = require('lodash');
var MockState = require('fixtures/mock_state');
var storeNames = {
app: 'appState',
global: 'globalState'
};
var queryFilter;
var $rootScope, appState, globalState;
beforeEach(module('kibana'));
beforeEach(function () {
appState = new MockState({ filters: [] });
globalState = new MockState({ filters: [] });
});
beforeEach(function () {
module('kibana/global_state', function ($provide) {
$provide.service('getAppState', function () {
return function () {
return appState;
};
});
$provide.service('globalState', function () {
return globalState;
});
});
});
beforeEach(function () {
inject(function (_$rootScope_, Private) {
$rootScope = _$rootScope_;
queryFilter = Private(require('components/filter_bar/query_filter'));
});
});
describe('getFilters method', function () {
var filters;
beforeEach(function () {
filters = [
{ query: { match: { extension: { query: 'jpg', type: 'phrase' } } } },
{ query: { match: { '@tags': { query: 'info', type: 'phrase' } } } }
];
});
it('should return app and global filters', function () {
appState.filters = [filters[0]];
globalState.filters = [filters[1]];
// global filters should be listed first
var res = queryFilter.getFilters();
expect(res[0]).to.eql(filters[1]);
expect(res[1]).to.eql(filters[0]);
// should return updated version of filters
var 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]];
var res = queryFilter.getFilters();
expect(res[0].$state.store).to.be(storeNames.global);
expect(res[1].$state.store).to.be(storeNames.app);
});
it('should return filters from specific states', function () {
var states = [
[ globalState, queryFilter.getGlobalFilters ],
[ appState, queryFilter.getAppFilters ],
];
_.each(states, function (state) {
state[0].filters = filters;
var res = state[1]();
expect(res.length).to.be(state[0].filters.length);
});
});
});
describe('filter reconciliation', function () {
var 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;
var appFilter = _.cloneDeep(filters[1]);
appState.filters.push(appFilter);
// global filters should be listed first
var 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;
var appFilter = _.cloneDeep(filters[1]);
appFilter.meta.negate = true;
appState.filters.push(appFilter);
// global filters should be listed first
var 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) {
var f = _.cloneDeep(filter);
f.meta.disabled = true;
return f;
});
_.each(filters, function (filter) { globalState.filters.push(filter); });
var res = queryFilter.getFilters();
expect(res).to.have.length(6);
// test adding to appState
globalState.filters = _.map(filters, function (filter) {
var 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);
});
});
}];
});

View file

@ -0,0 +1,114 @@
define(function (require) {
return ['invert filters', function () {
var _ = require('lodash');
var sinon = require('test_utils/auto_release_sinon');
var MockState = require('fixtures/mock_state');
var storeNames = {
app: 'appState',
global: 'globalState'
};
var filters;
var queryFilter;
var $rootScope, appState, globalState;
beforeEach(module('kibana'));
beforeEach(function () {
appState = new MockState({ filters: [] });
globalState = new MockState({ filters: [] });
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 }
}
];
});
beforeEach(function () {
module('kibana/global_state', function ($provide) {
$provide.service('getAppState', function () {
return function () {
return appState;
};
});
$provide.service('globalState', function () {
return globalState;
});
});
});
beforeEach(function () {
inject(function (_$rootScope_, Private) {
$rootScope = _$rootScope_;
queryFilter = Private(require('components/filter_bar/query_filter'));
});
});
describe('inverting a filter', function () {
it('should swap the negate property in appState', function () {
_.each(filters, function (filter) {
expect(filter.meta.negate).to.be(false);
appState.filters.push(filter);
});
queryFilter.invertFilter(filters[1]);
expect(appState.filters[1].meta.negate).to.be(true);
});
it('should toggle the negate property in globalState', function () {
_.each(filters, function (filter) {
expect(filter.meta.negate).to.be(false);
globalState.filters.push(filter);
});
queryFilter.invertFilter(filters[1]);
expect(globalState.filters[1].meta.negate).to.be(true);
});
it('should fire the update and fetch events', function () {
var emitSpy = sinon.spy(queryFilter, 'emit');
appState.filters = filters;
// set up the watchers
$rootScope.$digest();
queryFilter.invertFilter(filters[1]);
// trigger the digest loop to fire the watchers
$rootScope.$digest();
expect(emitSpy.callCount).to.be(2);
expect(emitSpy.firstCall.args[0]).to.be('update');
expect(emitSpy.secondCall.args[0]).to.be('fetch');
});
});
describe('bulk inverting', function () {
beforeEach(function () {
appState.filters = filters;
globalState.filters = _.map(_.cloneDeep(filters), function (filter) {
filter.meta.negate = true;
return filter;
});
});
it('should swap the negate state for all filters', function () {
queryFilter.invertAll();
_.each(appState.filters, function (filter) {
expect(filter.meta.negate).to.be(true);
});
_.each(globalState.filters, function (filter) {
expect(filter.meta.negate).to.be(false);
});
});
});
}];
});

View file

@ -0,0 +1,160 @@
define(function (require) {
return ['pin filters', function () {
var _ = require('lodash');
var sinon = require('test_utils/auto_release_sinon');
var MockState = require('fixtures/mock_state');
var storeNames = {
app: 'appState',
global: 'globalState'
};
var filters;
var queryFilter;
var $rootScope, appState, globalState;
beforeEach(module('kibana'));
beforeEach(function () {
appState = new MockState({ filters: [] });
globalState = new MockState({ filters: [] });
filters = [
{
query: { match: { extension: { query: 'gif', type: 'phrase' } } },
meta: { negate: false, disabled: false }
},
{
query: { match: { extension: { query: 'jpg', type: 'phrase' } } },
meta: { negate: true, disabled: false }
},
{
query: { match: { extension: { query: 'png', type: 'phrase' } } },
meta: { negate: true, disabled: true }
},
{
query: { match: { '@tags': { query: 'info', type: 'phrase' } } },
meta: { negate: false, disabled: false }
},
{
query: { match: { '@tags': { query: 'success', type: 'phrase' } } },
meta: { negate: false, disabled: false }
},
{
query: { match: { '@tags': { query: 'security', type: 'phrase' } } },
meta: { negate: true, disabled: false }
},
{
query: { match: { '_type': { query: 'nginx', type: 'phrase' } } },
meta: { negate: false, disabled: false }
},
{
query: { match: { '_type': { query: 'apache', type: 'phrase' } } },
meta: { negate: true, disabled: true }
}
];
});
beforeEach(function () {
module('kibana/global_state', function ($provide) {
$provide.service('getAppState', function () {
return function () {
return appState;
};
});
$provide.service('globalState', function () {
return globalState;
});
});
});
beforeEach(function () {
inject(function (_$rootScope_, Private) {
$rootScope = _$rootScope_;
queryFilter = Private(require('components/filter_bar/query_filter'));
});
});
describe('pin a filter', function () {
beforeEach(function () {
globalState.filters = _.filter(filters, function (filter) {
return !!filter.query.match._type;
});
appState.filters = _.filter(filters, function (filter) {
return !filter.query.match._type;
});
expect(globalState.filters).to.have.length(2);
expect(appState.filters).to.have.length(6);
});
it('should move filter from appState to globalState', function () {
var filter = appState.filters[1];
queryFilter.pinFilter(filter);
expect(globalState.filters).to.contain(filter);
expect(globalState.filters).to.have.length(3);
expect(appState.filters).to.have.length(5);
});
it('should move filter from globalState to appState', function () {
var filter = globalState.filters[1];
queryFilter.pinFilter(filter);
expect(appState.filters).to.contain(filter);
expect(globalState.filters).to.have.length(1);
expect(appState.filters).to.have.length(7);
});
it('should only fire the update event', function () {
var filter = appState.filters[1];
var emitSpy = sinon.spy(queryFilter, 'emit');
// set up the watchers
$rootScope.$digest();
queryFilter.pinFilter(filter);
// trigger the digest loop to fire the watchers
$rootScope.$digest();
expect(emitSpy.callCount).to.be(1);
expect(emitSpy.firstCall.args[0]).to.be('update');
});
});
describe('bulk pinning', function () {
beforeEach(function () {
globalState.filters = _.filter(filters, function (filter) {
return !!filter.query.match.extension;
});
appState.filters = _.filter(filters, function (filter) {
return !filter.query.match.extension;
});
expect(globalState.filters).to.have.length(3);
expect(appState.filters).to.have.length(5);
});
it('should swap the filters in both states', function () {
var appSample = _.sample(appState.filters);
var globalSample = _.sample(globalState.filters);
queryFilter.pinAll();
expect(globalState.filters).to.have.length(5);
expect(appState.filters).to.have.length(3);
expect(globalState.filters).to.contain(appSample);
expect(appState.filters).to.contain(globalSample);
});
it('should move all filters to globalState', function () {
queryFilter.pinAll(true);
expect(globalState.filters).to.have.length(8);
expect(appState.filters).to.have.length(0);
});
it('should move all filters to appState', function () {
queryFilter.pinAll(false);
expect(globalState.filters).to.have.length(0);
expect(appState.filters).to.have.length(8);
});
});
}];
});

View file

@ -0,0 +1,116 @@
define(function (require) {
return ['remove filters', function () {
var _ = require('lodash');
var sinon = require('test_utils/auto_release_sinon');
var MockState = require('fixtures/mock_state');
var storeNames = {
app: 'appState',
global: 'globalState'
};
var filters;
var queryFilter;
var $rootScope, appState, globalState;
beforeEach(module('kibana'));
beforeEach(function () {
appState = new MockState({ filters: [] });
globalState = new MockState({ filters: [] });
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 }
}
];
});
beforeEach(function () {
module('kibana/global_state', function ($provide) {
$provide.service('getAppState', function () {
return function () {
return appState;
};
});
$provide.service('globalState', function () {
return globalState;
});
});
});
beforeEach(function () {
inject(function (_$rootScope_, Private) {
$rootScope = _$rootScope_;
queryFilter = Private(require('components/filter_bar/query_filter'));
});
});
describe('removing a filter', function () {
it('should remove the filter from appState', function () {
appState.filters = filters;
expect(appState.filters).to.have.length(3);
queryFilter.removeFilter(filters[0]);
expect(appState.filters).to.have.length(2);
});
it('should remove the filter from globalState', function () {
globalState.filters = filters;
expect(globalState.filters).to.have.length(3);
queryFilter.removeFilter(filters[0]);
expect(globalState.filters).to.have.length(2);
});
it('should fire the update and fetch events', function () {
var emitSpy = sinon.spy(queryFilter, 'emit');
appState.filters = filters;
// set up the watchers
$rootScope.$digest();
queryFilter.removeFilter(filters[0]);
// trigger the digest loop to fire the watchers
$rootScope.$digest();
expect(emitSpy.callCount).to.be(2);
expect(emitSpy.firstCall.args[0]).to.be('update');
expect(emitSpy.secondCall.args[0]).to.be('fetch');
});
it('should only remove matching instances', function () {
globalState.filters.push(filters[0]);
globalState.filters.push(filters[1]);
appState.filters.push(filters[2]);
queryFilter.removeFilter(_.cloneDeep(filters[0]));
expect(globalState.filters).to.have.length(2);
expect(appState.filters).to.have.length(1);
queryFilter.removeFilter(_.cloneDeep(filters[2]));
expect(globalState.filters).to.have.length(2);
expect(appState.filters).to.have.length(1);
});
});
describe('bulk removal', function () {
it('should remove all the filters from both states', function () {
globalState.filters.push(filters[0]);
globalState.filters.push(filters[1]);
appState.filters.push(filters[2]);
expect(globalState.filters).to.have.length(2);
expect(appState.filters).to.have.length(1);
queryFilter.removeAll();
expect(globalState.filters).to.have.length(0);
expect(appState.filters).to.have.length(0);
});
});
}];
});

View file

@ -0,0 +1,158 @@
define(function (require) {
return ['toggle filters', function () {
var _ = require('lodash');
var sinon = require('test_utils/auto_release_sinon');
var MockState = require('fixtures/mock_state');
var storeNames = {
app: 'appState',
global: 'globalState'
};
var filters;
var queryFilter;
var $rootScope, appState, globalState;
beforeEach(module('kibana'));
beforeEach(function () {
appState = new MockState({ filters: [] });
globalState = new MockState({ filters: [] });
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 }
}
];
});
beforeEach(function () {
module('kibana/global_state', function ($provide) {
$provide.service('getAppState', function () {
return function () {
return appState;
};
});
$provide.service('globalState', function () {
return globalState;
});
});
});
beforeEach(function () {
inject(function (_$rootScope_, Private) {
$rootScope = _$rootScope_;
queryFilter = Private(require('components/filter_bar/query_filter'));
});
});
describe('toggling a filter', function () {
it('should toggle the disabled property in appState', function () {
_.each(filters, function (filter) {
expect(filter.meta.disabled).to.be(false);
appState.filters.push(filter);
});
queryFilter.toggleFilter(filters[1]);
expect(appState.filters[1].meta.disabled).to.be(true);
});
it('should toggle the disabled property in globalState', function () {
_.each(filters, function (filter) {
expect(filter.meta.disabled).to.be(false);
globalState.filters.push(filter);
});
queryFilter.toggleFilter(filters[1]);
expect(globalState.filters[1].meta.disabled).to.be(true);
});
it('should fire the update and fetch events', function () {
var emitSpy = sinon.spy(queryFilter, 'emit');
appState.filters = filters;
// set up the watchers
$rootScope.$digest();
queryFilter.toggleFilter(filters[1]);
// trigger the digest loop to fire the watchers
$rootScope.$digest();
expect(emitSpy.callCount).to.be(2);
expect(emitSpy.firstCall.args[0]).to.be('update');
expect(emitSpy.secondCall.args[0]).to.be('fetch');
});
it('should always enable the filter', function () {
appState.filters = filters.map(function (filter) {
filter.meta.disabled = true;
return filter;
});
expect(appState.filters[1].meta.disabled).to.be(true);
queryFilter.toggleFilter(filters[1], false);
expect(appState.filters[1].meta.disabled).to.be(false);
queryFilter.toggleFilter(filters[1], false);
expect(appState.filters[1].meta.disabled).to.be(false);
});
it('should always disable the filter', function () {
globalState.filters = filters;
expect(globalState.filters[1].meta.disabled).to.be(false);
queryFilter.toggleFilter(filters[1], true);
expect(globalState.filters[1].meta.disabled).to.be(true);
queryFilter.toggleFilter(filters[1], true);
expect(globalState.filters[1].meta.disabled).to.be(true);
});
});
describe('bulk toggling', function () {
beforeEach(function () {
appState.filters = filters;
globalState.filters = _.map(_.cloneDeep(filters), function (filter) {
filter.meta.disabled = true;
return filter;
});
});
it('should swap the enabled state for all filters', function () {
queryFilter.toggleAll();
_.each(appState.filters, function (filter) {
expect(filter.meta.disabled).to.be(true);
});
_.each(globalState.filters, function (filter) {
expect(filter.meta.disabled).to.be(false);
});
});
it('should enable all filters', function () {
queryFilter.toggleAll(true);
_.each(appState.filters, function (filter) {
expect(filter.meta.disabled).to.be(true);
});
_.each(globalState.filters, function (filter) {
expect(filter.meta.disabled).to.be(true);
});
});
it('should disable all filters', function () {
queryFilter.toggleAll(false);
_.each(appState.filters, function (filter) {
expect(filter.meta.disabled).to.be(false);
});
_.each(globalState.filters, function (filter) {
expect(filter.meta.disabled).to.be(false);
});
});
});
}];
});

View file

@ -3,7 +3,6 @@ define(function (require) {
describe('Filter Bar Directive', function () {
describe('dedupFilters(existing, filters)', function () {
it('should return only filters which are not in the existing', function () {
var existing = [
{ range: { bytes: { from: 0, to: 1024 } } },
@ -25,13 +24,26 @@ define(function (require) {
];
var filters = [
{ range: { bytes: { from: 1024, to: 2048 } } },
{ meta: { negate: false }, query: { match: { _term: { query: 'apache', type: 'phrase' } } } }
{ query: { match: { _term: { query: 'apache', type: 'phrase' } } } }
];
var results = dedupFilters(existing, filters);
expect(results).to.contain(filters[0]);
expect(results).to.not.contain(filters[1]);
});
it('should ignore $state attribute', function () {
var existing = [
{ range: { bytes: { from: 0, to: 1024 } } },
{ $state: { store: 'appState' }, query: { match: { _term: { query: 'apache', type: 'phrase' } } } }
];
var filters = [
{ range: { bytes: { from: 1024, to: 2048 } } },
{ $state: { store: 'globalState' }, query: { match: { _term: { query: 'apache', type: 'phrase' } } } }
];
var results = dedupFilters(existing, filters);
expect(results).to.contain(filters[0]);
expect(results).to.not.contain(filters[1]);
});
});
});
});

View file

@ -16,4 +16,4 @@ define(function (require) {
});
});
});
});

View file

@ -5,16 +5,31 @@ define(function (require) {
var $ = require('jquery');
require('components/filter_bar/filter_bar');
var MockState = require('fixtures/mock_state');
describe('Filter Bar Directive', function () {
var $rootScope, $compile, $timeout, Promise;
var appState, queryFilter, mapFilter, getIndexPatternStub, indexPattern, $el;
// require('test_utils/no_digest_promises').activateForSuite();
var $rootScope, $compile, getIndexPatternStub, indexPattern;
beforeEach(function () {
appState = new MockState({ filters: [] });
beforeEach(function (done) {
module('kibana/global_state', function ($provide) {
$provide.service('getAppState', function () {
return function () {
return appState;
};
});
});
});
beforeEach(function () {
// load the application
module('kibana');
getIndexPatternStub = sinon.stub();
module('kibana/courier', function ($provide) {
$provide.service('courier', function () {
var courier = { indexPatterns: { get: getIndexPatternStub } };
@ -22,38 +37,59 @@ define(function (require) {
});
});
inject(function (Promise, Private, _$rootScope_, _$compile_) {
inject(function (Private, $injector, _$rootScope_, _$compile_, _$timeout_) {
$rootScope = _$rootScope_;
$compile = _$compile_;
$timeout = _$timeout_;
Promise = $injector.get('Promise');
mapFilter = Private(require('components/filter_bar/lib/mapFilter'));
indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
getIndexPatternStub.returns(Promise.resolve(indexPattern));
$rootScope.state = {
filters: [
{ meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'apache' } } } },
{ meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'nginx' } } } },
{ meta: { index: 'logstash-*' }, exists: { field: '@timestamp' } },
{ meta: { index: 'logstash-*' }, missing: { field: 'host' }, disabled: true },
]
var queryFilter = Private(require('components/filter_bar/query_filter'));
queryFilter.getFilters = function () {
return appState.filters;
};
done();
});
});
it('should render all the filters in state', function () {
var el = $compile('<filter-bar state=state></filter-bar>')($rootScope);
$rootScope.$digest();
var filters = el.find('.filter');
expect(filters).to.have.length(4);
expect($(filters[0]).find('span')[0].innerHTML).to.equal('_type:');
expect($(filters[0]).find('span')[1].innerHTML).to.equal('"apache"');
expect($(filters[1]).find('span')[0].innerHTML).to.equal('_type:');
expect($(filters[1]).find('span')[1].innerHTML).to.equal('"nginx"');
expect($(filters[2]).find('span')[0].innerHTML).to.equal('exists:');
expect($(filters[2]).find('span')[1].innerHTML).to.equal('"@timestamp"');
expect($(filters[3]).find('span')[0].innerHTML).to.equal('missing:');
expect($(filters[3]).find('span')[1].innerHTML).to.equal('"host"');
});
describe('Element rendering', function () {
beforeEach(function (done) {
var filters = [
{ meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'apache' } } } },
{ meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'nginx' } } } },
{ meta: { index: 'logstash-*' }, exists: { field: '@timestamp' } },
{ meta: { index: 'logstash-*' }, missing: { field: 'host' }, disabled: true },
];
Promise.map(filters, mapFilter).then(function (filters) {
appState.filters = filters;
$el = $compile('<filter-bar></filter-bar>')($rootScope);
});
var off = $rootScope.$on('filterbar:updated', function () {
off();
// force a nextTick so it continues *after* the $digest loop completes
setTimeout(done, 0);
});
// kick off the digest loop
$rootScope.$digest();
});
it('should render all the filters in state', function () {
var filters = $el.find('.filter');
expect(filters).to.have.length(4);
expect($(filters[0]).find('span')[0].innerHTML).to.equal('_type:');
expect($(filters[0]).find('span')[1].innerHTML).to.equal('"apache"');
expect($(filters[1]).find('span')[0].innerHTML).to.equal('_type:');
expect($(filters[1]).find('span')[1].innerHTML).to.equal('"nginx"');
expect($(filters[2]).find('span')[0].innerHTML).to.equal('exists:');
expect($(filters[2]).find('span')[1].innerHTML).to.equal('"@timestamp"');
expect($(filters[3]).find('span')[0].innerHTML).to.equal('missing:');
expect($(filters[3]).find('span')[1].innerHTML).to.equal('"host"');
});
});
});
});

View file

@ -3,23 +3,95 @@ define(function (require) {
describe('Filter Bar Directive', function () {
describe('onlyDisabled()', function () {
it('should return true if all filters remove are disabled', function () {
it('should return true if all filters are disabled', function () {
var filters = [
{ meta: { disabled: true } },
{ meta: { disabled: true } },
{ meta: { disabled: true } }
];
var newFilters = [{ meta: { disabled: true } }];
expect(onlyDisabled(newFilters, filters)).to.be(true);
});
it('should return false if all filters are not disabled', function () {
var filters = [
{ meta: { disabled: false } },
{ meta: { disabled: false } },
{ meta: { disabled: false } }
];
var newFilters = [{ meta: { disabled: false } }];
expect(onlyDisabled(newFilters, filters)).to.be(false);
});
it('should return false if only old filters are disabled', function () {
var filters = [
{ meta: { disabled: true } },
{ meta: { disabled: true } },
{ meta: { disabled: true } }
];
var newFilters = [{ meta: { disabled: false } }];
expect(onlyDisabled(newFilters, filters)).to.be(false);
});
it('should return false if new filters are not disabled', function () {
var filters = [
{ meta: { disabled: false } },
{ meta: { disabled: false } },
{ meta: { disabled: false } }
];
var newFilters = [{ meta: { disabled: true } }];
expect(onlyDisabled(newFilters, filters)).to.be(false);
});
it('should return true when all removed filters were disabled', function () {
var filters = [
{ meta: { disabled: true } },
{ meta: { disabled: true } },
{ meta: { disabled: true } }
];
var newFilters = [];
expect(onlyDisabled(newFilters, filters)).to.be(true);
});
it('should return false when all removed filters were not disabled', function () {
var filters = [
{ meta: { disabled: false } },
{ meta: { disabled: false } },
{ meta: { disabled: false } }
];
var newFilters = [];
expect(onlyDisabled(newFilters, filters)).to.be(false);
});
it('should return true if all changed filters are disabled', function () {
var filters = [
{ meta: { disabled: true, negate: false } },
{ meta: { disabled: true, negate: false } }
];
var newFilters = [
{ meta: { disabled: true, negate: true } },
{ meta: { disabled: true, negate: true } }
];
expect(onlyDisabled(newFilters, filters)).to.be(true);
});
it('should return false if all filters remove were not disabled', function () {
var filters = [
{ meta: { disabled: false } },
{ meta: { disabled: false } },
{ meta: { disabled: true } }
];
var newFilters = [{ meta: { disabled: false } }];
expect(onlyDisabled(newFilters, filters)).to.be(false);
});
it('should return false when all removed filters are not disabled', function () {
var filters = [
{ meta: { disabled: true } },
{ meta: { disabled: false } },
{ meta: { disabled: true } }
];
var newFilters = [filters[1]];
expect(onlyDisabled(newFilters, filters)).to.be(true);
});
it('should return false if all filters remove are not disabled', function () {
var filters = [
{ meta: { disabled: true } },
{ meta: { disabled: false } },
{ meta: { disabled: false } }
];
var newFilters = [filters[1]];
var newFilters = [];
expect(onlyDisabled(newFilters, filters)).to.be(false);
});

View file

@ -0,0 +1,59 @@
define(function (require) {
var _ = require('lodash');
var queryFilter;
var EventEmitter;
var $rootScope;
describe('Query Filter', function () {
describe('Module', function () {
beforeEach(module('kibana'));
beforeEach(function () {
inject(function (_$rootScope_, Private) {
$rootScope = _$rootScope_;
queryFilter = Private(require('components/filter_bar/query_filter'));
EventEmitter = Private(require('factories/events'));
});
});
describe('module instance', function () {
it('should be an event emitter', function () {
expect(queryFilter).to.be.an(EventEmitter);
});
});
describe('module methods', function () {
it('should have methods for getting filters', function () {
expect(queryFilter.getFilters).to.be.a('function');
expect(queryFilter.getAppFilters).to.be.a('function');
expect(queryFilter.getGlobalFilters).to.be.a('function');
});
it('should have methods for modifying filters', function () {
expect(queryFilter.addFilters).to.be.a('function');
expect(queryFilter.toggleFilter).to.be.a('function');
expect(queryFilter.toggleAll).to.be.a('function');
expect(queryFilter.removeFilter).to.be.a('function');
expect(queryFilter.removeAll).to.be.a('function');
expect(queryFilter.invertFilter).to.be.a('function');
expect(queryFilter.invertAll).to.be.a('function');
expect(queryFilter.pinFilter).to.be.a('function');
expect(queryFilter.pinAll).to.be.a('function');
});
});
});
describe('Actions', function () {
var childSuites = [
require('specs/components/filter_bar/_getFilters'),
require('specs/components/filter_bar/_addFilters'),
require('specs/components/filter_bar/_removeFilters'),
require('specs/components/filter_bar/_toggleFilters'),
require('specs/components/filter_bar/_invertFilters'),
require('specs/components/filter_bar/_pinFilters'),
].forEach(function (s) {
describe(s[0], s[1]);
});
});
});
});

View file

@ -1,40 +0,0 @@
define(function (require) {
var removeAll = require('components/filter_bar/lib/removeAll');
describe('Filter Bar Directive', function () {
var $rootScope, $compile;
beforeEach(function (done) {
// load the application
module('kibana');
inject(function (_$rootScope_, _$compile_) {
$rootScope = _$rootScope_;
$compile = _$compile_;
$rootScope.state = {
filters: [
{ query: { match: { '@tags': { query: 'test' } } } },
{ query: { match: { '@tags': { query: 'bar' } } } },
{ exists: { field: '@timestamp' } },
{ missing: { field: 'host' }, meta: { disabled: true } },
]
};
done();
});
});
describe('removeAll', function () {
it('should remove all the filters', function () {
var fn = removeAll($rootScope);
expect($rootScope.state.filters).to.have.length(4);
fn();
expect($rootScope.state.filters).to.have.length(0);
});
});
});
});

View file

@ -1,40 +0,0 @@
define(function (require) {
var removeFilter = require('components/filter_bar/lib/removeFilter');
describe('Filter Bar Directive', function () {
var $rootScope, $compile;
beforeEach(function (done) {
// load the application
module('kibana');
inject(function (_$rootScope_, _$compile_) {
$rootScope = _$rootScope_;
$compile = _$compile_;
$rootScope.state = {
filters: [
{ query: { match: { '@tags': { query: 'test' } } } },
{ query: { match: { '@tags': { query: 'bar' } } } },
{ exists: { field: '@timestamp' } },
{ missing: { field: 'host' }, meta: { disabled: true } },
]
};
done();
});
});
describe('removeFilter', function () {
it('should remove the filter from the state', function () {
var filter = $rootScope.state.filters[2];
var fn = removeFilter($rootScope);
fn(filter);
expect($rootScope.state.filters).to.not.contain(filter);
});
});
});
});

View file

@ -1,84 +0,0 @@
define(function (require) {
describe('Filter Bar Directive', function () {
var toggleAll = require('components/filter_bar/lib/toggleAll');
var _ = require('lodash');
var sinon = require('test_utils/auto_release_sinon');
var mapFilter, $rootScope, $compile, Promise, getIndexPatternStub, indexPattern;
beforeEach(module('kibana'));
beforeEach(function () {
getIndexPatternStub = sinon.stub();
module('kibana/courier', function ($provide) {
$provide.service('courier', function () {
var courier = { indexPatterns: { get: getIndexPatternStub } };
return courier;
});
});
});
beforeEach(inject(function (_Promise_, _$rootScope_, _$compile_, Private) {
Promise = _Promise_;
mapFilter = Private(require('components/filter_bar/lib/mapFilter'));
indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
getIndexPatternStub.returns(Promise.resolve(indexPattern));
$rootScope = _$rootScope_;
$compile = _$compile_;
$rootScope.state = {
filters: [
{ meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'apache' } } } },
{ meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'nginx' } } } },
{ meta: { index: 'logstash-*' }, exists: { field: '@timestamp' } },
{ meta: { index: 'logstash-*', disabled: true }, missing: { field: 'host' } },
]
};
}));
describe('toggleAll', function () {
var fn;
beforeEach(function (done) {
var _filters = _($rootScope.state.filters)
.filter(function (filter) { return filter; })
.flatten(true)
.value();
Promise.map(_filters, mapFilter).then(function (filters) {
$rootScope.filters = filters;
done();
});
$rootScope.$apply();
});
beforeEach(function () {
fn = toggleAll($rootScope);
});
var pickDisabled = function (filter) {
return filter.meta.disabled;
};
it('should toggle all the filters', function () {
expect(_.filter($rootScope.state.filters, pickDisabled)).to.have.length(1);
fn();
expect(_.filter($rootScope.state.filters, pickDisabled)).to.have.length(3);
});
it('should disable all the filters', function () {
expect(_.filter($rootScope.state.filters, pickDisabled)).to.have.length(1);
fn(true);
expect(_.filter($rootScope.state.filters, pickDisabled)).to.have.length(4);
});
it('should enable all the filters', function () {
expect(_.filter($rootScope.state.filters, pickDisabled)).to.have.length(1);
fn(false);
expect(_.filter($rootScope.state.filters, pickDisabled)).to.have.length(0);
});
});
});
});

View file

@ -1,49 +0,0 @@
define(function (require) {
describe('Filter Bar Directive', function () {
var sinon = require('test_utils/auto_release_sinon');
var toggleFilter = require('components/filter_bar/lib/toggleFilter');
var $rootScope, $compile, mapFilter, getIndexPatternStub, indexPattern;
beforeEach(module('kibana'));
beforeEach(function () {
getIndexPatternStub = sinon.stub();
module('kibana/courier', function ($provide) {
$provide.service('courier', function () {
var courier = { indexPatterns: { get: getIndexPatternStub } };
return courier;
});
});
});
beforeEach(inject(function (Promise, Private, _$rootScope_, _$compile_) {
mapFilter = Private(require('components/filter_bar/lib/mapFilter'));
$rootScope = _$rootScope_;
$compile = _$compile_;
indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
getIndexPatternStub.returns(Promise.resolve(indexPattern));
$rootScope.state = {
filters: [
{ meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'apache' } } } },
{ meta: { index: 'logstash-*' }, query: { match: { '_type': { query: 'nginx' } } } },
{ meta: { index: 'logstash-*' }, exists: { field: '@timestamp' } },
{ missing: { field: 'host' }, meta: { disabled: true, index: 'logstash-*' } },
]
};
}));
describe('toggleFilter', function () {
it('should toggle filters on and off', function (done) {
var filter = $rootScope.state.filters[0];
var fn = toggleFilter($rootScope);
mapFilter(filter).then(fn).then(function (result) {
expect(result.meta).to.have.property('disabled', true);
done();
});
$rootScope.$apply();
});
});
});
});

View file

@ -12,6 +12,35 @@ define(function (require) {
expect(results).to.have.length(1);
});
it('should filter out duplicates, ignoring meta attributes', function () {
var before = [
{
meta: { negate: true },
query: { _type: { match: { query: 'apache', type: 'phrase' } } }
},
{
meta: { negate: false },
query: { _type: { match: { query: 'apache', type: 'phrase' } } }
}
];
var results = uniqFilters(before);
expect(results).to.have.length(1);
});
it('should filter out duplicates, ignoring $state attributes', function () {
var before = [
{
$state: { store: 'appState' },
query: { _type: { match: { query: 'apache', type: 'phrase' } } }
},
{
$state: { store: 'globalState' },
query: { _type: { match: { query: 'apache', type: 'phrase' } } }
}
];
var results = uniqFilters(before);
expect(results).to.have.length(1);
});
});
});
});

View file

@ -1,82 +0,0 @@
define(function (require) {
describe('Filter Bar watchFilters()', function () {
var sinon = require('test_utils/auto_release_sinon');
var _ = require('lodash');
var watchFilters;
var Promise;
var EventEmitter;
var $scope;
beforeEach(module('kibana'));
beforeEach(inject(function (Private, $injector) {
Promise = $injector.get('Promise');
EventEmitter = Private(require('factories/events'));
watchFilters = Private(require('components/filter_bar/lib/watchFilters'));
$scope = {
$watch: sinon.stub()
};
}));
it('returns an event emitter', function () {
expect(watchFilters($scope)).to.be.an(EventEmitter);
});
it('listens to the filters on state', function () {
watchFilters($scope, { update: _.noop, fetch: _.noop });
expect($scope.$watch).to.have.property('callCount', 1);
var call = $scope.$watch.getCall(0);
expect(call.args[0]).to.be('state.filters');
});
describe('change handling', function () {
require('test_utils/no_digest_promises').activateForSuite();
it('calls update and fetch', function () {
var onFetch = sinon.stub();
var onUpdate = sinon.stub();
watchFilters($scope).on('fetch', onFetch).on('update', onUpdate);
var handler = $scope.$watch.args[0][1];
return handler([ {} ], [])
.then(function () {
expect(onUpdate).to.have.property('callCount', 1);
expect(onFetch).to.have.property('callCount', 1);
});
});
it('only calls update if all filters are disabled', function () {
var onFetch = sinon.stub();
var onUpdate = sinon.stub();
watchFilters($scope).on('fetch', onFetch).on('update', onUpdate);
var handler = $scope.$watch.args[0][1];
return handler([ ], [ { meta: { disabled: true } } ])
.then(function () {
expect(onUpdate).to.have.property('callCount', 1);
expect(onFetch).to.have.property('callCount', 0);
});
});
it('calls nothing if there were no changes', function () {
var onFetch = sinon.stub();
var onUpdate = sinon.stub();
watchFilters($scope).on('fetch', onFetch).on('update', onUpdate);
var handler = $scope.$watch.args[0][1];
var cur = [];
var prev = cur;
return Promise.try(handler, [cur, prev])
.then(function () {
expect(onUpdate).to.have.property('callCount', 0);
expect(onFetch).to.have.property('callCount', 0);
});
});
});
});
});

View file

@ -1,18 +1,35 @@
define(function (require) {
var filterManager = require('components/filter_manager/filter_manager');
var $state;
var _ = require('lodash');
var MockState = require('fixtures/mock_state');
var filterManager;
var appState;
describe('Filter Manager', function () {
beforeEach(module('kibana'));
beforeEach(function () {
$state = {
filters: []
};
filterManager.init($state);
module('kibana/global_state', function ($provide) {
$provide.service('getAppState', function () {
return function () {
return appState;
};
});
});
});
it('should have an init function that sets the state to be used', function () {
expect(filterManager.init).to.be.a(Function);
filterManager.init($state);
expect(filterManager.$state).to.be($state);
beforeEach(function () {
inject(function (Private) {
filterManager = Private(require('components/filter_manager/filter_manager'));
appState = new MockState();
appState.filters = [];
// mock queryFilter's invertFilter since it's used in the manager
var queryFilter = Private(require('components/filter_bar/query_filter'));
queryFilter.invertFilter = function (filter) {
filter.meta.negate = !filter.meta.negate;
};
});
});
it('should have an `add` function', function () {
@ -20,35 +37,49 @@ define(function (require) {
});
it('should add a filter', function () {
expect($state.filters.length).to.be(0);
expect(appState.filters.length).to.be(0);
filterManager.add('myField', 1, '+', 'myIndex');
expect($state.filters.length).to.be(1);
expect(appState.filters.length).to.be(1);
});
it('should add multiple filters if passed an array of values', function () {
filterManager.add('myField', [1, 2, 3], '+', 'myIndex');
expect($state.filters.length).to.be(3);
expect(appState.filters.length).to.be(3);
});
it('should add an exists filter if _exists_ is used as the field', function () {
filterManager.add('_exists_', 'myField', '+', 'myIndex');
expect($state.filters[0].exists).to.eql({field: 'myField'});
expect(appState.filters[0].exists).to.eql({field: 'myField'});
});
it('Should negate existing filter instead of added a conflicting filter', function () {
filterManager.add('myField', 1, '+', 'myIndex');
expect($state.filters.length).to.be(1);
expect(appState.filters.length).to.be(1);
filterManager.add('myField', 1, '-', 'myIndex');
expect($state.filters.length).to.be(1);
expect($state.filters[0].meta.negate).to.be(true);
expect(appState.filters.length).to.be(1);
expect(appState.filters[0].meta.negate).to.be(true);
filterManager.add('_exists_', 'myField', '+', 'myIndex');
expect($state.filters.length).to.be(2);
expect(appState.filters.length).to.be(2);
filterManager.add('_exists_', 'myField', '-', 'myIndex');
expect($state.filters.length).to.be(2);
expect($state.filters[1].meta.negate).to.be(true);
expect(appState.filters.length).to.be(2);
expect(appState.filters[1].meta.negate).to.be(true);
});
it('should enable matching filters being changed', function () {
_.each([true, false], function (negate) {
appState.filters = [{
query: { match: { myField: { query: 1 } } },
meta: { disabled: true, negate: negate }
}];
expect(appState.filters.length).to.be(1);
expect(appState.filters[0].meta.disabled).to.be(true);
filterManager.add('myField', 1, '+', 'myIndex');
expect(appState.filters.length).to.be(1);
expect(appState.filters[0].meta.disabled).to.be(false);
});
});
});
});

View file

@ -0,0 +1,115 @@
define(function (require) {
var _ = require('lodash');
var fieldFormats;
var FieldFormat;
var config;
var formatIds = [
'bytes',
'date',
'ip',
'number',
'percent',
'string',
'url',
'_source'
];
return ['conformance', function () {
beforeEach(module('kibana'));
beforeEach(inject(function (Private, $injector) {
fieldFormats = Private(require('registry/field_formats'));
FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
config = $injector.get('config');
}));
formatIds.forEach(function (id) {
var instance;
var Type;
beforeEach(function () {
Type = fieldFormats.getType(id);
instance = fieldFormats.getInstance(id);
});
describe(id + ' Type', function () {
it('has an id', function () {
expect(Type.id).to.be.a('string');
});
it('has a title', function () {
expect(Type.title).to.be.a('string');
});
it('declares compatible field formats as a string or array', function () {
expect(Type.fieldType).to.be.ok();
expect(_.isString(Type.fieldType) || _.isArray(Type.fieldType)).to.be(true);
});
});
describe(id + ' Instance', function () {
it('extends FieldFormat', function () {
expect(instance).to.be.a(FieldFormat);
});
});
});
it('registers all of the fieldFormats', function () {
expect(_.difference(fieldFormats.raw, formatIds.map(fieldFormats.getType))).to.eql([]);
});
describe('Bytes format', basicPatternTests('bytes', require('numeral')));
describe('Percent Format', basicPatternTests('percent', require('numeral')));
describe('Date Format', basicPatternTests('date', require('moment')));
describe('Number Format', function () {
basicPatternTests('number', require('numeral'))();
it('tries to parse strings', function () {
var number = new (fieldFormats.getType('number'))({ pattern: '0.0b' });
expect(number.convert(123.456)).to.be('123.5B');
expect(number.convert('123.456')).to.be('123.5B');
});
});
function basicPatternTests(id, lib) {
var confKey = id === 'date' ? 'dateFormat' : 'format:' + id + ':defaultPattern';
return function () {
it('converts using the format:' + id + ':defaultPattern config', function () {
var inst = fieldFormats.getInstance(id);
[
'0b',
'0 b',
'0.[000] b',
'0.[000]b',
'0.[0]b'
].forEach(function (pattern) {
var num = _.random(-10000, 10000, true);
config.set(confKey, pattern);
expect(inst.convert(num)).to.be(lib(num).format(pattern));
});
});
it('uses the pattern param if available', function () {
var num = _.random(-10000, 10000, true);
var defFormat = '0b';
var customFormat = '0.00000%';
config.set(confKey, defFormat);
var defInst = fieldFormats.getInstance(id);
var Type = fieldFormats.getType(id);
var customInst = new Type({ pattern: customFormat });
expect(defInst.convert(num)).to.not.be(customInst.convert(num));
expect(defInst.convert(num)).to.be(lib(num).format(defFormat));
expect(customInst.convert(num)).to.be(lib(num).format(customFormat));
});
};
}
}];
});

View file

@ -0,0 +1,16 @@
define(function (require) {
return ['IP Address Format', function () {
var fieldFormats;
beforeEach(module('kibana'));
beforeEach(inject(function (Private) {
fieldFormats = Private(require('registry/field_formats'));
}));
it('convers a value from a decimal to a string', function () {
var ip = fieldFormats.getInstance('ip');
expect(ip.convert(1186489492)).to.be('70.184.100.148');
});
}];
});

View file

@ -0,0 +1,34 @@
define(function (require) {
return ['_source formatting', function () {
var $ = require('jquery');
var _ = require('lodash');
var fieldFormats;
beforeEach(module('kibana'));
beforeEach(inject(function (Private) {
fieldFormats = Private(require('registry/field_formats'));
}));
describe('Source format', function () {
var indexPattern;
var hits;
var format;
var convertHtml;
beforeEach(inject(function (Private) {
indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
hits = Private(require('fixtures/hits'));
format = fieldFormats.getInstance('_source');
convertHtml = format.getConverterFor('html');
}));
it('uses the _source, field, and hit to create a <dl>', function () {
var hit = _.first(hits);
var $dl = $(convertHtml(hit._source, indexPattern.fields.byName._source, hit));
expect($dl.is('dl')).to.be.ok();
expect($dl.find('dt')).to.have.length(_.keys(indexPattern.flattenHit(hit)).length);
});
});
}];
});

View file

@ -0,0 +1,119 @@
define(function (require) {
return ['Url Format', function () {
var $ = require('jquery');
var fieldFormats;
beforeEach(module('kibana'));
beforeEach(inject(function (Private) {
fieldFormats = Private(require('registry/field_formats'));
}));
describe('Url Format', function () {
var Url;
beforeEach(function () {
Url = fieldFormats.getType('url');
});
it('ouputs a simple <a> tab by default', function () {
var url = new Url();
var $a = $(url.convert('http://elastic.co', 'html'));
expect($a.is('a')).to.be(true);
expect($a.size()).to.be(1);
expect($a.attr('href')).to.be('http://elastic.co');
expect($a.attr('target')).to.be('_blank');
expect($a.children().size()).to.be(0);
});
it('outputs an <image> if type === "img"', function () {
var url = new Url({ type: 'img' });
var $img = $(url.convert('http://elastic.co', 'html'));
expect($img.is('img')).to.be(true);
expect($img.attr('src')).to.be('http://elastic.co');
});
describe('url template', function () {
it('accepts a template', function () {
var url = new Url({ urlTemplate: 'url: {{ value }}' });
var $a = $(url.convert('url', 'html'));
expect($a.is('a')).to.be(true);
expect($a.size()).to.be(1);
expect($a.attr('href')).to.be('url: url');
expect($a.attr('target')).to.be('_blank');
expect($a.children().size()).to.be(0);
});
it('only outputs the url if the contentType === "text"', function () {
var url = new Url();
expect(url.convert('url', 'text')).to.be('url');
});
});
describe('label template', function () {
it('accepts a template', function () {
var url = new Url({ labelTemplate: 'extension: {{ value }}' });
var $a = $(url.convert('php', 'html'));
expect($a.is('a')).to.be(true);
expect($a.size()).to.be(1);
expect($a.attr('href')).to.be('php');
expect($a.html()).to.be('extension: php');
});
it('uses the label template for text formating', function () {
var url = new Url({ labelTemplate: 'external {{value }}'});
expect(url.convert('url', 'text')).to.be('external url');
});
it('can use the raw value', function () {
var url = new Url({
labelTemplate: 'external {{value}}'
});
expect(url.convert('url?', 'text')).to.be('external url?');
});
it('can use the url', function () {
var url = new Url({
urlTemplate: 'http://google.com/{{value}}',
labelTemplate: 'external {{url}}'
});
expect(url.convert('url?', 'text')).to.be('external http://google.com/url%3F');
});
});
describe('templating', function () {
it('ignores unknown variables', function () {
var url = new Url({ urlTemplate: '{{ not really a var }}' });
expect(url.convert('url', 'text')).to.be('');
});
it('does not allow executing code in variable expressions', function () {
window.SHOULD_NOT_BE_TRUE = false;
var url = new Url({ urlTemplate: '{{ (window.SHOULD_NOT_BE_TRUE = true) && value }}' });
expect(url.convert('url', 'text')).to.be('');
});
describe('', function () {
before(function () {
Object.prototype.cantStopMeNow = {
toString: function () {
return 'fail';
},
cantStopMeNow: null
};
});
it('does not get values from the prototype chain', function () {
var url = new Url({ urlTemplate: '{{ cantStopMeNow }}' });
expect(url.convert('url', 'text')).to.be('');
});
after(function () {
delete Object.prototype.cantStopMeNow;
});
});
});
});
}];
});

View file

@ -1,229 +1,12 @@
define(function (require) {
describe('Stringify Component', function () {
var _ = require('lodash');
var $ = require('jquery');
run(require('specs/components/stringify/_conformance'));
run(require('specs/components/stringify/_ip'));
run(require('specs/components/stringify/_source'));
run(require('specs/components/stringify/_url'));
var fieldFormats;
var FieldFormat;
var config;
var $rootScope;
var formatIds = [
'bytes',
'date',
'ip',
'number',
'percent',
'string',
'url',
'_source'
];
beforeEach(module('kibana'));
beforeEach(inject(function (Private, $injector) {
fieldFormats = Private(require('registry/field_formats'));
FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
config = $injector.get('config');
$rootScope = $injector.get('$rootScope');
}));
it('registers all of the fieldFormats', function () {
expect(_.difference(fieldFormats.raw, formatIds.map(fieldFormats.getType))).to.eql([]);
});
describe('conformance', function () {
formatIds.forEach(function (id) {
var instance;
var Type;
beforeEach(function () {
Type = fieldFormats.getType(id);
instance = fieldFormats.getInstance(id);
});
describe(id + ' Type', function () {
it('has an id', function () {
expect(Type.id).to.be.a('string');
});
it('has a title', function () {
expect(Type.title).to.be.a('string');
});
it('declares compatible field formats as a string or array', function () {
expect(Type.fieldType).to.be.ok();
expect(_.isString(Type.fieldType) || _.isArray(Type.fieldType)).to.be(true);
});
});
describe(id + ' Instance', function () {
it('extends FieldFormat', function () {
expect(instance).to.be.a(FieldFormat);
});
});
});
});
describe('Bytes format', basicPatternTests('bytes', require('numeral')));
describe('Percent Format', basicPatternTests('percent', require('numeral')));
describe('Date Format', basicPatternTests('date', require('moment')));
describe('Number Format', function () {
basicPatternTests('number', require('numeral'))();
it('tries to parse strings', function () {
var number = new (fieldFormats.getType('number'))({ pattern: '0.0b' });
expect(number.convert(123.456)).to.be('123.5B');
expect(number.convert('123.456')).to.be('123.5B');
});
});
function basicPatternTests(id, lib) {
var confKey = id === 'date' ? 'dateFormat' : 'format:' + id + ':defaultPattern';
return function () {
it('converts using the format:' + id + ':defaultPattern config', function () {
var inst = fieldFormats.getInstance(id);
[
'0b',
'0 b',
'0.[000] b',
'0.[000]b',
'0.[0]b'
].forEach(function (pattern) {
var num = _.random(-10000, 10000, true);
config.set(confKey, pattern);
expect(inst.convert(num)).to.be(lib(num).format(pattern));
});
});
it('uses the pattern param if available', function () {
var num = _.random(-10000, 10000, true);
var defFormat = '0b';
var customFormat = '0.00000%';
config.set(confKey, defFormat);
var defInst = fieldFormats.getInstance(id);
var Type = fieldFormats.getType(id);
var customInst = new Type({ pattern: customFormat });
expect(defInst.convert(num)).to.not.be(customInst.convert(num));
expect(defInst.convert(num)).to.be(lib(num).format(defFormat));
expect(customInst.convert(num)).to.be(lib(num).format(customFormat));
});
};
function run(suite) {
describe(suite[0], suite[1]);
}
describe('Ip format', function () {
it('convers a value from a decimal to a string', function () {
var ip = fieldFormats.getInstance('ip');
expect(ip.convert(1186489492)).to.be('70.184.100.148');
});
});
describe('Url Format', function () {
var Url;
beforeEach(function () {
Url = fieldFormats.getType('url');
});
it('ouputs a simple <a> tab by default', function () {
var url = new Url();
var $a = $(url.convert('http://elastic.co', 'html'));
expect($a.is('a')).to.be(true);
expect($a.size()).to.be(1);
expect($a.attr('href')).to.be('http://elastic.co');
expect($a.attr('target')).to.be('_blank');
expect($a.children().size()).to.be(0);
});
it('outputs an <image> if type === "img"', function () {
var url = new Url({ type: 'img' });
var $img = $(url.convert('http://elastic.co', 'html'));
expect($img.is('img')).to.be(true);
expect($img.attr('src')).to.be('http://elastic.co');
});
it('only outputs the url if the contentType === "text"', function () {
var url = new Url();
expect(url.convert('url', 'text')).to.be('url');
});
describe('template', function () {
it('accepts a template', function () {
var url = new Url({ template: 'url: {{ value }}' });
var $a = $(url.convert('url', 'html'));
expect($a.is('a')).to.be(true);
expect($a.size()).to.be(1);
expect($a.attr('href')).to.be('url: url');
expect($a.attr('target')).to.be('_blank');
expect($a.children().size()).to.be(0);
});
it('renders for text contentType', function () {
var url = new Url({ template: 'url: {{ value }}' });
expect(url.convert('url', 'text')).to.be('url: url');
});
it('ignores unknown variables', function () {
var url = new Url({ template: '{{ not really a var }}' });
expect(url.convert('url', 'text')).to.be('');
});
it('does not allow executing code in variable expressions', function () {
window.SHOULD_NOT_BE_TRUE = false;
var url = new Url({ template: '{{ (window.SHOULD_NOT_BE_TRUE = true) && value }}' });
expect(url.convert('url', 'text')).to.be('');
});
describe('', function () {
before(function () {
Object.prototype.cantStopMeNow = {
toString: function () {
return 'fail';
},
cantStopMeNow: null
};
});
it('does not get values from the prototype chain', function () {
var url = new Url({ template: '{{ cantStopMeNow }}' });
expect(url.convert('url', 'text')).to.be('');
});
after(function () {
delete Object.prototype.cantStopMeNow;
});
});
});
});
describe('Source format', function () {
var indexPattern;
var hits;
var format;
var convertHtml;
beforeEach(inject(function (Private) {
indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
hits = Private(require('fixtures/hits'));
format = fieldFormats.getInstance('_source');
convertHtml = format.getConverterFor('html');
}));
it('uses the _source, field, and hit to create a <dl>', function () {
var hit = _.first(hits);
var $dl = $(convertHtml(hit._source, indexPattern.fields.byName._source, hit));
expect($dl.is('dl')).to.be.ok();
expect($dl.find('dt')).to.have.length(_.keys(indexPattern.flattenHit(hit)).length);
});
});
});
});

View file

@ -1,4 +1,5 @@
define(function (require) {
var _ = require('lodash');
describe('$scope.$watchMulti', function () {
var sinon = require('test_utils/auto_release_sinon');
@ -11,119 +12,189 @@ define(function (require) {
$scope = $rootScope.$new();
}));
it('exposes $watchMulti on all scopes', function () {
expect($rootScope.$watchMulti).to.be.a('function');
expect($scope).to.have.property('$watchMulti', $rootScope.$watchMulti);
describe('basic functionality', function () {
it('exposes $watchMulti on all scopes', function () {
expect($rootScope.$watchMulti).to.be.a('function');
expect($scope).to.have.property('$watchMulti', $rootScope.$watchMulti);
var $isoScope = $scope.$new(true);
expect($isoScope).to.have.property('$watchMulti', $rootScope.$watchMulti);
});
it('only triggers a single watch on initialization', function () {
var stub = sinon.stub();
$scope.$watchMulti([
'one',
'two',
'three'
], stub);
$rootScope.$apply();
expect(stub.callCount).to.be(1);
});
it('only triggers a single watch when multiple values change', function () {
var stub = sinon.spy(function (a, b) {});
$scope.$watchMulti([
'one',
'two',
'three'
], stub);
$rootScope.$apply();
expect(stub.callCount).to.be(1);
$scope.one = 'a';
$scope.two = 'b';
$scope.three = 'c';
$rootScope.$apply();
expect(stub.callCount).to.be(2);
});
it('passes an array of the current values as the first arg, and an array of the previous values as the second',
function () {
var stub = sinon.spy(function (a, b) {});
$scope.one = 'a';
$scope.two = 'b';
$scope.three = 'c';
$scope.$watchMulti([
'one',
'two',
'three'
], stub);
$rootScope.$apply();
expect(stub.firstCall.args).to.eql([
['a', 'b', 'c'],
['a', 'b', 'c']
]);
$scope.one = 'do';
$scope.two = 're';
$scope.three = 'mi';
$rootScope.$apply();
expect(stub.secondCall.args).to.eql([
['do', 're', 'mi'],
['a', 'b', 'c']
]);
});
it('the current value is always up to date', function () {
var count = 0;
$scope.vals = [1, 0];
$scope.$watchMulti([ 'vals[0]', 'vals[1]' ], function (cur, prev) {
expect(cur).to.eql($scope.vals);
count++;
var $isoScope = $scope.$new(true);
expect($isoScope).to.have.property('$watchMulti', $rootScope.$watchMulti);
});
var $child = $scope.$new();
$child.$watch('vals[0]', function (cur) {
$child.vals[1] = cur;
});
it('returns a working unwatch function', function () {
$scope.a = 0;
$scope.b = 0;
var triggers = 0;
var unwatch = $scope.$watchMulti(['a', 'b'], function () { triggers++; });
$rootScope.$apply();
expect(count).to.be(2);
// initial watch
$scope.$apply();
expect(triggers).to.be(1);
// prove that it triggers on chagne
$scope.a++;
$scope.$apply();
expect(triggers).to.be(2);
// remove watchers
expect($scope.$$watchers).to.not.eql([]);
unwatch();
expect($scope.$$watchers).to.eql([]);
// prove that it doesn't trigger anymore
$scope.a++;
$scope.$apply();
expect(triggers).to.be(2);
});
});
it('returns a working unwatch function', function () {
$scope.a = 0;
$scope.b = 0;
var triggers = 0;
var unwatch = $scope.$watchMulti(['a', 'b'], function () { triggers++; });
describe('simple scope watchers', function () {
it('only triggers a single watch on initialization', function () {
var stub = sinon.stub();
// initial watch
$scope.$apply();
expect(triggers).to.be(1);
$scope.$watchMulti([
'one',
'two',
'three'
], stub);
$rootScope.$apply();
// prove that it triggers on chagne
$scope.a++;
$scope.$apply();
expect(triggers).to.be(2);
expect(stub.callCount).to.be(1);
});
// remove watchers
expect($scope.$$watchers).to.not.eql([]);
unwatch();
expect($scope.$$watchers).to.eql([]);
it('only triggers a single watch when multiple values change', function () {
var stub = sinon.spy(function (a, b) {});
// prove that it doesn't trigger anymore
$scope.a++;
$scope.$apply();
expect(triggers).to.be(2);
$scope.$watchMulti([
'one',
'two',
'three'
], stub);
$rootScope.$apply();
expect(stub.callCount).to.be(1);
$scope.one = 'a';
$scope.two = 'b';
$scope.three = 'c';
$rootScope.$apply();
expect(stub.callCount).to.be(2);
});
it('passes an array of the current and previous values, in order',
function () {
var stub = sinon.spy(function (a, b) {});
$scope.one = 'a';
$scope.two = 'b';
$scope.three = 'c';
$scope.$watchMulti([
'one',
'two',
'three'
], stub);
$rootScope.$apply();
expect(stub.firstCall.args).to.eql([
['a', 'b', 'c'],
['a', 'b', 'c']
]);
$scope.one = 'do';
$scope.two = 're';
$scope.three = 'mi';
$rootScope.$apply();
expect(stub.secondCall.args).to.eql([
['do', 're', 'mi'],
['a', 'b', 'c']
]);
});
it('always has an up to date value', function () {
var count = 0;
$scope.vals = [1, 0];
$scope.$watchMulti([ 'vals[0]', 'vals[1]' ], function (cur, prev) {
expect(cur).to.eql($scope.vals);
count++;
});
var $child = $scope.$new();
$child.$watch('vals[0]', function (cur) {
$child.vals[1] = cur;
});
$rootScope.$apply();
expect(count).to.be(2);
});
});
describe('complex watch expressions', function () {
var stateWatchers;
var firstValue;
var secondValue;
beforeEach(function () {
var firstGetter = function () {
return firstValue;
};
var secondGetter = function () {
return secondValue;
};
stateWatchers = [{
fn: $rootScope.$watch,
get: firstGetter
}, {
fn: $rootScope.$watch,
get: secondGetter
}];
});
it('should trigger the watcher on initialization', function () {
var stub = sinon.stub();
firstValue = 'first';
secondValue = 'second';
$scope.$watchMulti(stateWatchers, stub);
$rootScope.$apply();
expect(stub.callCount).to.be(1);
expect(stub.firstCall.args[0]).to.eql([firstValue, secondValue]);
expect(stub.firstCall.args[1]).to.eql([firstValue, secondValue]);
});
});
describe('nested watchers', function () {
it('should trigger the handler at least once', function () {
var $scope = $rootScope.$new();
$scope.$$watchers = [{
get: _.noop,
fn: _.noop,
eq: false,
last: false
}, {
get: _.noop,
fn: registerWatchers,
eq: false,
last: false
}];
var first = sinon.stub();
var second = sinon.stub();
function registerWatchers() {
$scope.$watchMulti([first, second], function () {
expect(first.callCount).to.be.greaterThan(0);
expect(second.callCount).to.be.greaterThan(0);
});
}
$scope.$digest();
});
});
});
});

View file

@ -2,6 +2,7 @@ define(function (require) {
var sinon = require('test_utils/auto_release_sinon');
var faker = require('faker');
var _ = require('lodash');
var MockState = require('fixtures/mock_state');
// global vars, injected and mocked in init()
var kbnUrl;
@ -29,8 +30,7 @@ define(function (require) {
});
$provide.service('globalState', function () {
globalStateMock = {};
globalStateMock.on = globalStateMock.off = _.noop;
globalStateMock = new MockState();
globalStateMock.removeFromUrl = function (url) {
return url;
};

View file

@ -0,0 +1,15 @@
define(function (require) {
return ['_.pushAll', function () {
var _ = require('lodash');
it('pushes an entire array into another', function () {
var a = [1, 2, 3, 4];
var b = [5, 6, 7, 8];
var output = _.pushAll(b, a);
expect(output).to.be(a);
expect(a).to.eql([1, 2, 3, 4, 5, 6, 7, 8]);
expect(b).to.eql([5, 6, 7, 8]);
});
}];
});

View file

@ -2,6 +2,7 @@ define(function (require) {
describe('lodash mixins', function () {
run(require('specs/utils/mixins/_move'));
run(require('specs/utils/mixins/_organize_by'));
run(require('specs/utils/mixins/_push_all'));
function run(m) { describe(m[0], m[1]); }
});
});
});