Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selectable list directive #6630

Merged
merged 26 commits into from
Mar 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9c7c4d9
init commit
stormpython Mar 15, 2016
1aab5e0
troubleshooting
stormpython Mar 15, 2016
8455d56
almost ready
stormpython Mar 16, 2016
3aa06ab
removing commented code
stormpython Mar 16, 2016
041c2f6
adding back sort functionality to saved object and paginated selectab…
stormpython Mar 17, 2016
6939960
fixing issue with list not sorting
stormpython Mar 17, 2016
27c6f17
refactoring html and css
stormpython Mar 17, 2016
ec68aa0
Merge branch 'feature/design' into selectable_list_directive
stormpython Mar 17, 2016
1ff22b1
refactoring to allow for array of objects, removing useless css styles
stormpython Mar 17, 2016
242fe85
Merge branch 'feature/design' into selectable_list_directive
stormpython Mar 17, 2016
420c1bc
adding tests
stormpython Mar 18, 2016
00f5527
Merge branch 'feature/design' into selectable_list_directive
stormpython Mar 18, 2016
b968b0b
adding more tests, refactoring directive
stormpython Mar 19, 2016
1dc9a18
Merge branch 'feature/design' of github.com:elastic/kibana into selec…
stormpython Mar 22, 2016
149cc2e
fixing issue with background hover on paginated-selectable-list and s…
stormpython Mar 22, 2016
08bae15
Merge branch 'master' into selectable_list_directive
stormpython Mar 22, 2016
f458af2
Merge branch 'master' into selectable_list_directive
stormpython Mar 23, 2016
66a8c7f
Merge branch 'master' into selectable_list_directive
stormpython Mar 23, 2016
2992a66
adding label filter for input on saved object filter placeholder
stormpython Mar 23, 2016
d90baae
removing div and adding display block to anchor tag to allow the enti…
stormpython Mar 23, 2016
8f1f209
Merge branch 'master' into selectable_list_directive
stormpython Mar 23, 2016
3640960
Merge branch 'master' into selectable_list_directive
stormpython Mar 24, 2016
73a8adb
adding error case and test
stormpython Mar 24, 2016
6f0892d
adding an error case and more tests
stormpython Mar 24, 2016
00c3f21
Merge branch 'master' into selectable_list_directive
stormpython Mar 24, 2016
98003ae
refactoring tests
stormpython Mar 24, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/plugins/kibana/public/visualize/styles/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
padding: 0;
display: flex;

div.wizard-small {
flex: 2;
}

div.wizard-large {
flex: 3;
}

.wizard-column {
flex: 1;
display: flex;
Expand Down Expand Up @@ -45,11 +53,6 @@

.list-group {
margin-bottom: 0;

.list-group-item {
border-radius: 0;
border: none;
}
}

.striped {
Expand Down
23 changes: 9 additions & 14 deletions src/plugins/kibana/public/visualize/wizard/step_2.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
<bread-crumbs></bread-crumbs>
<div class="wizard">
<div class="wizard-column">
<h3>From a New Search</h3>
<!-- Index patterns -->
<div class="wizard-row">
<div class="panel panel-default">
<div class="panel-heading">Index Patterns</div>
</div>
<ul class="striped list-group">
<li class="list-group-item" ng-repeat="pattern in indexPattern.list | orderBy: 'toString()'">
<a class="index-link" kbn-href="{{ makeUrl(pattern) }}">{{pattern}}</a>
</li>
</ul>
</div>
<div class="wizard-small wizard-column">
<h3>From a New Search, Select Index</h3>
<paginated-selectable-list
per-page="20"
list="indexPattern.list"
user-make-url="makeUrl"
class="wizard-row">
</paginated-selectable-list>
</div>
<div class="wizard-column">
<div class="wizard-large wizard-column">
<h3>Or, From a Saved Search</h3>
<!-- Saved searches -->
<saved-object-finder
Expand Down
1 change: 1 addition & 0 deletions src/plugins/kibana/public/visualize/wizard/wizard.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import _ from 'lodash';
import 'plugins/kibana/visualize/saved_visualizations/saved_visualizations';
import 'ui/directives/saved_object_finder';
import 'ui/directives/paginated_selectable_list';
import 'plugins/kibana/discover/saved_searches/saved_searches';
import routes from 'ui/routes';
import RegistryVisTypesProvider from 'ui/registry/vis_types';
Expand Down
183 changes: 183 additions & 0 deletions src/ui/public/directives/__tests__/paginated_selectable_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import angular from 'angular';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import _ from 'lodash';

var objectList = [
{ title: 'apple' },
{ title: 'orange' },
{ title: 'coconut' },
{ title: 'banana' },
{ title: 'grapes' }
];

var stringList = [
'apple',
'orange',
'coconut',
'banana',
'grapes'
];

var lists = [objectList, stringList, []];

var $scope;
var $element;
var $isolatedScope;

lists.forEach(function (list) {
var isArrayOfObjects = list.every((item) => {
return _.isPlainObject(item);
});

var init = function (arr, willFail) {
// Load the application
ngMock.module('kibana');

// Create the scope
ngMock.inject(function ($rootScope, $compile) {
$scope = $rootScope.$new();
$scope.perPage = 5;
$scope.list = list;
$scope.listProperty = isArrayOfObjects ? 'title' : undefined;
$scope.test = function (val) { return val; };

// Create the element
if (willFail) {
$element = angular.element('<paginated-selectable-list per-page="perPage" list="list"' +
'list-property="listProperty" user-make-url="test" user-on-select="test"></paginated-selectable-list>');
} else {
$element = angular.element('<paginated-selectable-list per-page="perPage" list="list"' +
'list-property="listProperty" user-make-url="test"></paginated-selectable-list>');
}

// And compile it
$compile($element)($scope);

// Fire a digest cycle
$element.scope().$digest();

// Grab the isolate scope so we can test it
$isolatedScope = $element.isolateScope();
});
};

describe('paginatedSelectableList', function () {
it('should throw an error when there is no makeUrl and onSelect attribute', ngMock.inject(function ($compile, $rootScope) {
function errorWrapper() {
$compile(angular.element('<paginated-selectable-list></paginated-selectable-list>'))($rootScope.new());
}
expect(errorWrapper).to.throwError();
}));

it('should throw an error with both makeUrl and onSelect attributes', function () {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can parameterize the init function and get rid of this duplication. Then you could just expect(init).to.throwError()

function errorWrapper() {
init(list, true);
}
expect(errorWrapper).to.throwError();
});

describe('$scope.hits', function () {
beforeEach(function () {
init(list);
});

it('should initially sort an array of objects in ascending order', function () {
var property = $isolatedScope.listProperty;
var sortedList = property ? _.sortBy(list, property) : _.sortBy(list);

expect($isolatedScope.hits).to.be.an('array');

$isolatedScope.hits.forEach(function (hit, index) {
if (property) {
expect(hit[property]).to.equal(sortedList[index][property]);
} else {
expect(hit).to.equal(sortedList[index]);
}
});
});
});

describe('$scope.sortHits', function () {
beforeEach(function () {
init(list);
});

it('should sort an array of objects in ascending order', function () {
var property = $isolatedScope.listProperty;
var sortedList = property ? _.sortBy(list, property) : _.sortBy(list);

$isolatedScope.isAscending = false;
$isolatedScope.sortHits(list);

expect($isolatedScope.isAscending).to.be(true);

$isolatedScope.hits.forEach(function (hit, index) {
if (property) {
expect(hit[property]).to.equal(sortedList[index][property]);
} else {
expect(hit).to.equal(sortedList[index]);
}
});
});

it('should sort an array of objects in descending order', function () {
var property = $isolatedScope.listProperty;
var reversedList = property ? _.sortBy(list, property).reverse() : _.sortBy(list).reverse();

$isolatedScope.isAscending = true;
$isolatedScope.sortHits(list);

expect($isolatedScope.isAscending).to.be(false);

$isolatedScope.hits.forEach(function (hit, index) {
if (property) {
expect(hit[property]).to.equal(reversedList[index][property]);
} else {
expect(hit).to.equal(reversedList[index]);
}
});
});
});

describe('$scope.makeUrl', function () {
beforeEach(function () {
init(list);
});

it('should return the result of the function its passed', function () {
var property = $isolatedScope.listProperty;
var sortedList = property ? _.sortBy(list, property) : _.sortBy(list);

$isolatedScope.hits.forEach(function (hit, index) {
if (property) {
expect($isolatedScope.makeUrl(hit)[property]).to.equal(sortedList[index][property]);
} else {
expect($isolatedScope.makeUrl(hit)).to.equal(sortedList[index]);
}
});
});
});

describe('$scope.onSelect', function () {
beforeEach(function () {
init(list);
});

it('should return the result of the function its passed', function () {
var property = $isolatedScope.listProperty;
var sortedList = property ? _.sortBy(list, property) : _.sortBy(list);

$isolatedScope.userOnSelect = function (val) { return val; };

$isolatedScope.hits.forEach(function (hit, index) {
if (property) {
expect($isolatedScope.onSelect(hit)[property]).to.equal(sortedList[index][property]);
} else {
expect($isolatedScope.onSelect(hit)).to.equal(sortedList[index]);
}
});
});
});
});
});
2 changes: 0 additions & 2 deletions src/ui/public/directives/paginate.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,5 +194,3 @@ uiModules.get('kibana')
template: paginateControlsTemplate
};
});


71 changes: 71 additions & 0 deletions src/ui/public/directives/paginated_selectable_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import _ from 'lodash';
import uiModules from 'ui/modules';
import paginatedSelectableListTemplate from 'ui/partials/paginated_selectable_list.html';

const module = uiModules.get('kibana');

function throwError(message) {
throw new Error(message);
}

module.directive('paginatedSelectableList', function (kbnUrl) {

return {
restrict: 'E',
scope: {
perPage: '=?',
list: '=',
listProperty: '=',
userMakeUrl: '=?',
userOnSelect: '=?'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a check in the controller to see if userMakeUrl && userOnSelect have been passed, throw an error if they have. Add a test to check that behavior.

},
template: paginatedSelectableListTemplate,
controller: function ($scope, $element, $filter) {
// Should specify either user-make-url or user-on-select
if (!$scope.userMakeUrl && !$scope.userOnSelect) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't check if they're both specified?

throwError('paginatedSelectableList directive expects a makeUrl or onSelect function');
}

// Should specify either user-make-url or user-on-select, but not both.
if ($scope.userMakeUrl && $scope.userOnSelect) {
throwError('paginatedSelectableList directive expects a makeUrl or onSelect attribute but not both');
}

$scope.perPage = $scope.perPage || 10;
$scope.hits = $scope.list = _.sortBy($scope.list, accessor);
$scope.hitCount = $scope.hits.length;

/**
* Boolean that keeps track of whether hits are sorted ascending (true)
* or descending (false)
* * @type {Boolean}
*/
$scope.isAscending = true;

/**
* Sorts saved object finder hits either ascending or descending
* @param {Array} hits Array of saved finder object hits
* @return {Array} Array sorted either ascending or descending
*/
$scope.sortHits = function (hits) {
const sortedList = _.sortBy(hits, accessor);

$scope.isAscending = !$scope.isAscending;
$scope.hits = $scope.isAscending ? sortedList : sortedList.reverse();
};

$scope.makeUrl = function (hit) {
return $scope.userMakeUrl(hit);
};

$scope.onSelect = function (hit, $event) {
return $scope.userOnSelect(hit, $event);
};

function accessor(val) {
const prop = $scope.listProperty;
return prop ? val[prop] : val;
}
}
};
});
43 changes: 43 additions & 0 deletions src/ui/public/partials/paginated_selectable_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<form role="form" class="form-inline">
<div class="container-fluid">
<div class="row">
<div class="input-group form-group finder-form col-md-9">
<span class="input-group-addon">
<i class="fa fa-search"></i>
</span>
<input
input-focus
ng-model="query"
placeholder="Filter..."
class="form-control"
name="query"
type="text"
autocomplete="off" />
</div>
<div class="finder-hit-count col-md-3">
<span>{{ (hits | filter: query).length }} of {{ hitCount }}</span>
</div>
</div>
</div>
</form>
<paginate list="hits | filter: query" per-page="{{ perPage }}">
<ul class="li-striped list-group list-group-menu">
<li class="list-group-item" ng-click="sortHits(hits)">
<span class="paginate-heading">
Name
<i class="fa" ng-class="isAscending ? 'fa-caret-up' : 'fa-caret-down'"></i>
</span>
</li>
<li class="list-group-item list-group-menu-item" ng-repeat="hit in page">
<a ng-show="userMakeUrl" kbn-href="{{ makeUrl(hit) }}">
<span>{{ hit }}</span>
</a>
<div ng-show="userOnSelect" ng-click="onSelect(hit, $event)">
<span>{{ hit }}</span>
</div>
</li>
<li class="list-group-item list-group-no-results" ng-if="(hits | filter: query).length === 0">
<p>No matches found.</p>
</li>
</ul>
</paginate>
Loading