mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Factor out column manipulation in the doc table (#11006)
Backports PR #10681 * Factor out column manipulation in the doc table This refactoring effort turns the implicit manipulation of the set of columns that was scattered throughout the doc table and the field chooser into explicit function calls. This brings with it the following improvements: * The column manipulation code is not duplicated (DRY) * The controller stays in control (IOC) * If the required functions are not provided by the controller, manipulation of the columns is disabled. Additionally, the `discover_field` now uses a properly isolated scope instead of accessing inherited properties of the scope. * Make filter addition and removal in tests more reliable * Change function name to plural, move up ng-if * Remove inconsistent variable initialization * Fix function name typo * Save the state in the action instead of a $watch
This commit is contained in:
parent
10ac953d29
commit
eb40f7d249
22 changed files with 319 additions and 119 deletions
|
@ -21,11 +21,13 @@
|
|||
<i class="fa fa-search-plus text-muted"></i>
|
||||
<i class="fa fa-search-minus text-muted"></i>
|
||||
</span>
|
||||
<span ng-if="columns">
|
||||
<i ng-click="toggleColumn(field)"
|
||||
tooltip="Toggle column in table"
|
||||
<span ng-if="canToggleColumns()">
|
||||
<i
|
||||
class="fa fa-columns"
|
||||
ng-click="toggleColumn(field)"
|
||||
tooltip-append-to-body="1"
|
||||
class="fa fa-columns"></i>
|
||||
tooltip="Toggle column in table"
|
||||
></i>
|
||||
</span>
|
||||
<span ng-if="!indexPattern.metaFields.includes(field)">
|
||||
<i ng-click="filter('_exists_', field, '+')"
|
||||
|
|
|
@ -13,7 +13,9 @@ docViewsRegistry.register(function () {
|
|||
hit: '=',
|
||||
indexPattern: '=',
|
||||
filter: '=',
|
||||
columns: '='
|
||||
columns: '=',
|
||||
onAddColumn: '=',
|
||||
onRemoveColumn: '=',
|
||||
},
|
||||
controller: function ($scope) {
|
||||
$scope.mapping = $scope.indexPattern.fields.byName;
|
||||
|
@ -21,8 +23,19 @@ docViewsRegistry.register(function () {
|
|||
$scope.formatted = $scope.indexPattern.formatHit($scope.hit);
|
||||
$scope.fields = _.keys($scope.flattened).sort();
|
||||
|
||||
$scope.toggleColumn = function (fieldName) {
|
||||
_.toggleInOut($scope.columns, fieldName);
|
||||
$scope.canToggleColumns = function canToggleColumn() {
|
||||
return (
|
||||
_.isFunction($scope.onAddColumn)
|
||||
&& _.isFunction($scope.onRemoveColumn)
|
||||
);
|
||||
};
|
||||
|
||||
$scope.toggleColumn = function toggleColumn(columnName) {
|
||||
if ($scope.columns.includes(columnName)) {
|
||||
$scope.onRemoveColumn(columnName);
|
||||
} else {
|
||||
$scope.onAddColumn(columnName);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.showArrayInObjectsWarning = function (row, field) {
|
||||
|
|
|
@ -66,7 +66,10 @@
|
|||
render-counter
|
||||
class="panel-content"
|
||||
filter="filter"
|
||||
on-add-column="addColumn"
|
||||
on-change-sort-order="setSortOrder"
|
||||
on-move-column="moveColumn"
|
||||
on-remove-column="removeColumn"
|
||||
>
|
||||
</doc-table>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import _ from 'lodash';
|
||||
import 'ui/visualize';
|
||||
import 'ui/doc_table';
|
||||
import * as columnActions from 'ui/doc_table/actions/columns';
|
||||
import 'plugins/kibana/dashboard/panel/get_object_loaders_for_dashboard';
|
||||
import FilterManagerProvider from 'ui/filter_manager';
|
||||
import uiModules from 'ui/modules';
|
||||
|
@ -108,16 +109,27 @@ uiModules
|
|||
$scope.panel.columns = $scope.panel.columns || $scope.savedObj.columns;
|
||||
$scope.panel.sort = $scope.panel.sort || $scope.savedObj.sort;
|
||||
|
||||
// If the user updates the sort direction or columns in a saved search, we want to save that
|
||||
// to the ui state so the share url will show our temporary modifications.
|
||||
$scope.$watchCollection('panel.columns', function () {
|
||||
$scope.saveState();
|
||||
});
|
||||
|
||||
$scope.setSortOrder = function setSortOrder(columnName, direction) {
|
||||
$scope.panel.sort = [columnName, direction];
|
||||
$scope.saveState();
|
||||
};
|
||||
|
||||
$scope.addColumn = function addColumn(columnName) {
|
||||
$scope.savedObj.searchSource.get('index').popularizeField(columnName, 1);
|
||||
columnActions.addColumn($scope.panel.columns, columnName);
|
||||
$scope.saveState(); // sync to sharing url
|
||||
};
|
||||
|
||||
$scope.removeColumn = function removeColumn(columnName) {
|
||||
$scope.savedObj.searchSource.get('index').popularizeField(columnName, 1);
|
||||
columnActions.removeColumn($scope.panel.columns, columnName);
|
||||
$scope.saveState(); // sync to sharing url
|
||||
};
|
||||
|
||||
$scope.moveColumn = function moveColumn(columnName, newIndex) {
|
||||
columnActions.moveColumn($scope.panel.columns, columnName, newIndex);
|
||||
$scope.saveState(); // sync to sharing url
|
||||
};
|
||||
}
|
||||
|
||||
$scope.filter = function (field, value, operator) {
|
||||
|
|
|
@ -17,24 +17,32 @@ describe('discoverField', function () {
|
|||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private, $rootScope, $compile) {
|
||||
$elem = angular.element('<discover-field></discover-field>');
|
||||
$elem = angular.element(`
|
||||
<discover-field
|
||||
field="field"
|
||||
on-add-field="addField"
|
||||
on-remove-field="removeField"
|
||||
on-show-details="showDetails"
|
||||
></discover-field>
|
||||
`);
|
||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
|
||||
_.assign($rootScope, {
|
||||
field: indexPattern.fields.byName.extension,
|
||||
increaseFieldCounter: sinon.spy(),
|
||||
toggle: function (field) {
|
||||
indexPattern.fields.byName[field].display = !indexPattern.fields.byName[field].display;
|
||||
}
|
||||
addField: sinon.spy(() => $rootScope.field.display = true),
|
||||
removeField: sinon.spy(() => $rootScope.field.display = false),
|
||||
showDetails: sinon.spy(() => $rootScope.field.details = { exists: true }),
|
||||
});
|
||||
|
||||
$compile($elem)($rootScope);
|
||||
|
||||
$scope = $elem.scope();
|
||||
$scope = $elem.isolateScope();
|
||||
$scope.$digest();
|
||||
sinon.spy($scope, 'toggleDetails');
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
$scope.toggleDetails.restore();
|
||||
$scope.$destroy();
|
||||
});
|
||||
|
||||
|
@ -43,14 +51,27 @@ describe('discoverField', function () {
|
|||
expect($scope.toggleDisplay).to.be.a(Function);
|
||||
});
|
||||
|
||||
it('should toggle the display of the field', function () {
|
||||
it('should call onAddField or onRemoveField depending on the display state', function () {
|
||||
$scope.toggleDisplay($scope.field);
|
||||
expect($scope.field.display).to.be(true);
|
||||
expect($scope.onAddField.callCount).to.be(1);
|
||||
expect($scope.onAddField.firstCall.args).to.eql([$scope.field.name]);
|
||||
|
||||
$scope.toggleDisplay($scope.field);
|
||||
expect($scope.onRemoveField.callCount).to.be(1);
|
||||
expect($scope.onRemoveField.firstCall.args).to.eql([$scope.field.name]);
|
||||
});
|
||||
|
||||
it('should increase the field popularity', function () {
|
||||
it('should call toggleDetails when currently showing the details', function () {
|
||||
$scope.toggleDetails($scope.field);
|
||||
$scope.toggleDisplay($scope.field);
|
||||
expect($scope.increaseFieldCounter.called).to.be(true);
|
||||
expect($scope.toggleDetails.callCount).to.be(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleDetails', function () {
|
||||
it('should notify the parent when showing the details', function () {
|
||||
$scope.toggleDetails($scope.field);
|
||||
expect($scope.onShowDetails.callCount).to.be(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -37,18 +37,20 @@ const destroy = function () {
|
|||
};
|
||||
|
||||
describe('discover field chooser directives', function () {
|
||||
const $elem = angular.element(
|
||||
'<disc-field-chooser' +
|
||||
' columns="columns"' +
|
||||
' toggle="toggle"' +
|
||||
' hits="hits"' +
|
||||
' field-counts="fieldCounts"' +
|
||||
' filter="filter"' +
|
||||
' index-pattern="indexPattern"' +
|
||||
' index-pattern-list="indexPatternList"' +
|
||||
' state="state">' +
|
||||
'</disc-field-chooser>'
|
||||
);
|
||||
const $elem = angular.element(`
|
||||
<disc-field-chooser
|
||||
columns="columns"
|
||||
toggle="toggle"
|
||||
hits="hits"
|
||||
field-counts="fieldCounts"
|
||||
index-pattern="indexPattern"
|
||||
index-pattern-list="indexPatternList"
|
||||
state="state"
|
||||
on-add-field="addField"
|
||||
on-add-filter="addFilter"
|
||||
on-remove-field="removeField"
|
||||
></disc-field-chooser>
|
||||
`);
|
||||
|
||||
beforeEach(ngMock.module('kibana', ($provide) => {
|
||||
$provide.decorator('config', ($delegate) => {
|
||||
|
@ -81,9 +83,11 @@ describe('discover field chooser directives', function () {
|
|||
toggle: sinon.spy(),
|
||||
hits: hits,
|
||||
fieldCounts: fieldCounts,
|
||||
filter: sinon.spy(),
|
||||
addField: sinon.spy(),
|
||||
addFilter: sinon.spy(),
|
||||
indexPattern: indexPattern,
|
||||
indexPatternList: indexPatternList
|
||||
indexPatternList: indexPatternList,
|
||||
removeField: sinon.spy(),
|
||||
});
|
||||
|
||||
$scope.$digest();
|
||||
|
@ -195,37 +199,37 @@ describe('discover field chooser directives', function () {
|
|||
field = getField();
|
||||
});
|
||||
|
||||
it('should have a details function', function () {
|
||||
expect($scope.details).to.be.a(Function);
|
||||
it('should have a computeDetails function', function () {
|
||||
expect($scope.computeDetails).to.be.a(Function);
|
||||
});
|
||||
|
||||
it('should increase the field popularity when called', function () {
|
||||
indexPattern.popularizeField = sinon.spy();
|
||||
$scope.details(field);
|
||||
$scope.computeDetails(field);
|
||||
expect(indexPattern.popularizeField.called).to.be(true);
|
||||
});
|
||||
|
||||
it('should append a details object to the field', function () {
|
||||
$scope.details(field);
|
||||
$scope.computeDetails(field);
|
||||
expect(field.details).to.not.be(undefined);
|
||||
});
|
||||
|
||||
it('should delete the field details if they already exist', function () {
|
||||
$scope.details(field);
|
||||
$scope.computeDetails(field);
|
||||
expect(field.details).to.not.be(undefined);
|
||||
$scope.details(field);
|
||||
$scope.computeDetails(field);
|
||||
expect(field.details).to.be(undefined);
|
||||
});
|
||||
|
||||
it('... unless recompute is true', function () {
|
||||
$scope.details(field);
|
||||
$scope.computeDetails(field);
|
||||
expect(field.details).to.not.be(undefined);
|
||||
$scope.details(field, true);
|
||||
$scope.computeDetails(field, true);
|
||||
expect(field.details).to.not.be(undefined);
|
||||
});
|
||||
|
||||
it('should create buckets with formatted and raw values', function () {
|
||||
$scope.details(field);
|
||||
$scope.computeDetails(field);
|
||||
expect(field.details.buckets).to.not.be(undefined);
|
||||
expect(field.details.buckets[0].value).to.be(40.141592);
|
||||
expect(field.details.buckets[0].display).to.be('40.142');
|
||||
|
@ -239,7 +243,7 @@ describe('discover field chooser directives', function () {
|
|||
$scope.$apply();
|
||||
|
||||
field = getField();
|
||||
$scope.details(field);
|
||||
$scope.computeDetails(field);
|
||||
expect(getField().details.total).to.be(1);
|
||||
|
||||
$scope.hits = [
|
||||
|
|
|
@ -14,9 +14,16 @@ app.directive('discoverField', function ($compile) {
|
|||
restrict: 'E',
|
||||
template: html,
|
||||
replace: true,
|
||||
scope: {
|
||||
field: '=',
|
||||
onAddField: '=',
|
||||
onAddFilter: '=',
|
||||
onRemoveField: '=',
|
||||
onShowDetails: '=',
|
||||
},
|
||||
link: function ($scope, $elem) {
|
||||
let detailsElem;
|
||||
let detailScope = $scope.$new();
|
||||
let detailScope;
|
||||
|
||||
|
||||
const init = function () {
|
||||
|
@ -60,9 +67,11 @@ app.directive('discoverField', function ($compile) {
|
|||
};
|
||||
|
||||
$scope.toggleDisplay = function (field) {
|
||||
// This is inherited from fieldChooser
|
||||
$scope.toggle(field.name);
|
||||
if (field.display) $scope.increaseFieldCounter(field);
|
||||
if (field.display) {
|
||||
$scope.onRemoveField(field.name);
|
||||
} else {
|
||||
$scope.onAddField(field.name);
|
||||
}
|
||||
|
||||
if (field.details) {
|
||||
$scope.toggleDetails(field);
|
||||
|
@ -71,9 +80,7 @@ app.directive('discoverField', function ($compile) {
|
|||
|
||||
$scope.toggleDetails = function (field, recompute) {
|
||||
if (_.isUndefined(field.details) || recompute) {
|
||||
// This is inherited from fieldChooser
|
||||
$scope.details(field, recompute);
|
||||
detailScope.$destroy();
|
||||
$scope.onShowDetails(field, recompute);
|
||||
detailScope = $scope.$new();
|
||||
detailScope.warnings = getWarnings(field);
|
||||
|
||||
|
@ -82,6 +89,7 @@ app.directive('discoverField', function ($compile) {
|
|||
$elem.append(detailsElem).addClass('active');
|
||||
} else {
|
||||
delete field.details;
|
||||
detailScope.$destroy();
|
||||
detailsElem.remove();
|
||||
$elem.removeClass('active');
|
||||
}
|
||||
|
|
|
@ -24,7 +24,14 @@
|
|||
<h5>Selected Fields</h5>
|
||||
</div>
|
||||
<ul class="list-unstyled discover-selected-fields" >
|
||||
<discover-field ng-repeat="field in fields.raw|filter:{display:true}">
|
||||
<discover-field
|
||||
ng-repeat="field in fields.raw|filter:{display:true}"
|
||||
field="field"
|
||||
on-add-field="onAddField"
|
||||
on-add-filter="onAddFilter"
|
||||
on-remove-field="onRemoveField"
|
||||
on-show-details="computeDetails"
|
||||
>
|
||||
</discover-field>
|
||||
</ul>
|
||||
|
||||
|
@ -108,7 +115,13 @@
|
|||
<h6>Popular</h6>
|
||||
</li>
|
||||
<discover-field
|
||||
ng-repeat="field in popularFields | filter:filter.isFieldFiltered">
|
||||
ng-repeat="field in popularFields | filter:filter.isFieldFiltered"
|
||||
field="field"
|
||||
on-add-field="onAddField"
|
||||
on-add-filter="onAddFilter"
|
||||
on-remove-field="onRemoveField"
|
||||
on-show-details="computeDetails"
|
||||
>
|
||||
</discover-field>
|
||||
</ul>
|
||||
|
||||
|
@ -116,7 +129,13 @@
|
|||
ng-class="{ 'hidden-sm': !showFields, 'hidden-xs': !showFields }"
|
||||
class="list-unstyled discover-unpopular-fields">
|
||||
<discover-field
|
||||
ng-repeat="field in unpopularFields | filter:filter.isFieldFiltered">
|
||||
ng-repeat="field in unpopularFields | filter:filter.isFieldFiltered"
|
||||
field="field"
|
||||
on-add-field="onAddField"
|
||||
on-add-filter="onAddFilter"
|
||||
on-remove-field="onRemoveField"
|
||||
on-show-details="computeDetails"
|
||||
>
|
||||
</discover-field>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -26,7 +26,9 @@ app.directive('discFieldChooser', function ($location, globalState, config, $rou
|
|||
state: '=',
|
||||
indexPattern: '=',
|
||||
indexPatternList: '=',
|
||||
updateFilterInQuery: '=filter'
|
||||
onAddField: '=',
|
||||
onAddFilter: '=',
|
||||
onRemoveField: '=',
|
||||
},
|
||||
template: fieldChooserTemplate,
|
||||
link: function ($scope) {
|
||||
|
@ -98,11 +100,6 @@ app.directive('discFieldChooser', function ($location, globalState, config, $rou
|
|||
filter.active = filter.getActive();
|
||||
});
|
||||
|
||||
$scope.toggle = function (fieldName) {
|
||||
$scope.increaseFieldCounter(fieldName);
|
||||
_.toggleInOut($scope.columns, fieldName);
|
||||
};
|
||||
|
||||
$scope.$watchMulti([
|
||||
'[]fieldCounts',
|
||||
'[]columns',
|
||||
|
@ -156,7 +153,7 @@ app.directive('discFieldChooser', function ($location, globalState, config, $rou
|
|||
$scope.indexPattern.popularizeField(fieldName, 1);
|
||||
};
|
||||
|
||||
$scope.vizLocation = function (field) {
|
||||
function getVisualizeUrl(field) {
|
||||
if (!$scope.state) {return '';}
|
||||
|
||||
let agg = {};
|
||||
|
@ -210,16 +207,21 @@ app.directive('discFieldChooser', function ($location, globalState, config, $rou
|
|||
}
|
||||
})
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
$scope.details = function (field, recompute) {
|
||||
$scope.computeDetails = function (field, recompute) {
|
||||
if (_.isUndefined(field.details) || recompute) {
|
||||
field.details = fieldCalculator.getFieldValueCounts({
|
||||
hits: $scope.hits,
|
||||
field: field,
|
||||
count: 5,
|
||||
grouped: false
|
||||
});
|
||||
field.details = Object.assign(
|
||||
{
|
||||
visualizeUrl: field.visualizable ? getVisualizeUrl(field) : null,
|
||||
},
|
||||
fieldCalculator.getFieldValueCounts({
|
||||
hits: $scope.hits,
|
||||
field: field,
|
||||
count: 5,
|
||||
grouped: false
|
||||
}),
|
||||
);
|
||||
_.each(field.details.buckets, function (bucket) {
|
||||
bucket.display = field.format.convert(bucket.value);
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
(
|
||||
<a
|
||||
ng-show="!indexPattern.metaFields.includes(field.name)"
|
||||
ng-click="updateFilterInQuery('_exists_', field.name, '+')">
|
||||
ng-click="onAddFilter('_exists_', field.name, '+')">
|
||||
{{::field.details.exists}}
|
||||
</a>
|
||||
<span
|
||||
|
@ -26,9 +26,9 @@
|
|||
<div>
|
||||
<span ng-show="field.filterable" class="pull-right">
|
||||
<i aria-hidden="true" class="fa fa-search-minus pull-right discover-field-details-filter"
|
||||
ng-click="updateFilterInQuery(field, bucket.value, '-')" data-test-subj="minus-{{::field.name}}-{{::bucket.display}}"></i>
|
||||
ng-click="onAddFilter(field, bucket.value, '-')" data-test-subj="minus-{{::field.name}}-{{::bucket.display}}"></i>
|
||||
<i aria-hidden="true" class="fa fa-search-plus pull-right discover-field-details-filter"
|
||||
ng-click="updateFilterInQuery(field, bucket.value, '+')" data-test-subj="plus-{{::field.name}}-{{::bucket.display}}"></i>
|
||||
ng-click="onAddFilter(field, bucket.value, '+')" data-test-subj="plus-{{::field.name}}-{{::bucket.display}}"></i>
|
||||
</span>
|
||||
<div css-truncate css-truncate-expandable="true" class="discover-field-details-value" title="{{::bucket.display}}">
|
||||
{{::bucket.display}} <i ng-show="bucket.display === ''">Empty string</i>
|
||||
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
|
||||
<a
|
||||
ng-href="{{vizLocation(field)}}"
|
||||
ng-href="{{field.details.visualizeUrl}}"
|
||||
ng-show="field.visualizable"
|
||||
class="sidebar-item-button primary"
|
||||
data-test-subj="fieldVisualize-{{::field.name}}">
|
||||
|
|
|
@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||
import angular from 'angular';
|
||||
import moment from 'moment';
|
||||
import getSort from 'ui/doc_table/lib/get_sort';
|
||||
import * as columnActions from 'ui/doc_table/actions/columns';
|
||||
import dateMath from '@elastic/datemath';
|
||||
import 'ui/doc_table';
|
||||
import 'ui/visualize';
|
||||
|
@ -485,6 +486,20 @@ function discoverController($scope, config, courier, $route, $window, Notifier,
|
|||
filterManager.add(field, values, operation, $state.index);
|
||||
};
|
||||
|
||||
$scope.addColumn = function addColumn(columnName) {
|
||||
$scope.indexPattern.popularizeField(columnName, 1);
|
||||
columnActions.addColumn($scope.state.columns, columnName);
|
||||
};
|
||||
|
||||
$scope.removeColumn = function removeColumn(columnName) {
|
||||
$scope.indexPattern.popularizeField(columnName, 1);
|
||||
columnActions.removeColumn($scope.state.columns, columnName);
|
||||
};
|
||||
|
||||
$scope.moveColumn = function moveColumn(columnName, newIndex) {
|
||||
columnActions.moveColumn($scope.state.columns, columnName, newIndex);
|
||||
};
|
||||
|
||||
$scope.toTop = function () {
|
||||
$window.scrollTo(0, 0);
|
||||
};
|
||||
|
|
|
@ -61,10 +61,13 @@
|
|||
columns="state.columns"
|
||||
hits="rows"
|
||||
field-counts="fieldCounts"
|
||||
filter="filterQuery"
|
||||
index-pattern="searchSource.get('index')"
|
||||
index-pattern-list="opts.indexPatternList"
|
||||
state="state">
|
||||
state="state"
|
||||
on-add-field="addColumn"
|
||||
on-add-filter="filterQuery"
|
||||
on-remove-field="removeColumn"
|
||||
>
|
||||
</disc-field-chooser>
|
||||
</div>
|
||||
|
||||
|
@ -132,7 +135,10 @@
|
|||
data-title="{{opts.savedSearch.lastSavedTitle}}"
|
||||
data-description="{{opts.savedSearch.description}}"
|
||||
render-counter
|
||||
on-add-column="addColumn"
|
||||
on-change-sort-order="setSortOrder"
|
||||
on-move-column="moveColumn"
|
||||
on-remove-column="removeColumn"
|
||||
></doc-table>
|
||||
|
||||
<div ng-if="rows.length == opts.sampleSize" class="discover-table-footer">
|
||||
|
|
|
@ -100,6 +100,8 @@ describe('Doc Table', function () {
|
|||
index-pattern="indexPattern"
|
||||
sort-order="sortOrder"
|
||||
on-change-sort-order="onChangeSortOrder"
|
||||
on-move-column="moveColumn"
|
||||
on-remove-column="removeColumn"
|
||||
></thead>
|
||||
`);
|
||||
|
||||
|
@ -108,6 +110,8 @@ describe('Doc Table', function () {
|
|||
columns: [],
|
||||
sortOrder: [],
|
||||
onChangeSortOrder: sinon.stub(),
|
||||
moveColumn: sinon.spy(),
|
||||
removeColumn: sinon.spy(),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -199,34 +203,25 @@ describe('Doc Table', function () {
|
|||
});
|
||||
|
||||
it('should move columns to the right', function () {
|
||||
|
||||
$scope.moveRight('bytes');
|
||||
expect($scope.columns[1]).to.be('bytes');
|
||||
|
||||
$scope.moveRight('bytes');
|
||||
expect($scope.columns[2]).to.be('bytes');
|
||||
$scope.moveColumnRight('bytes');
|
||||
expect($scope.onMoveColumn.callCount).to.be(1);
|
||||
expect($scope.onMoveColumn.firstCall.args).to.eql(['bytes', 1]);
|
||||
});
|
||||
|
||||
it('shouldnt move the last column to the right', function () {
|
||||
expect($scope.columns[3]).to.be('point');
|
||||
|
||||
$scope.moveRight('point');
|
||||
expect($scope.columns[3]).to.be('point');
|
||||
$scope.moveColumnRight('point');
|
||||
expect($scope.onMoveColumn.callCount).to.be(0);
|
||||
});
|
||||
|
||||
it('should move columns to the left', function () {
|
||||
$scope.moveLeft('@timestamp');
|
||||
expect($scope.columns[1]).to.be('@timestamp');
|
||||
|
||||
$scope.moveLeft('request_body');
|
||||
expect($scope.columns[1]).to.be('request_body');
|
||||
$scope.moveColumnLeft('@timestamp');
|
||||
expect($scope.onMoveColumn.callCount).to.be(1);
|
||||
expect($scope.onMoveColumn.firstCall.args).to.eql(['@timestamp', 1]);
|
||||
});
|
||||
|
||||
it('shouldnt move the first column to the left', function () {
|
||||
expect($scope.columns[0]).to.be('bytes');
|
||||
|
||||
$scope.moveLeft('bytes');
|
||||
expect($scope.columns[0]).to.be('bytes');
|
||||
$scope.moveColumnLeft('bytes');
|
||||
expect($scope.onMoveColumn.callCount).to.be(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
32
src/ui/public/doc_table/actions/columns.js
Normal file
32
src/ui/public/doc_table/actions/columns.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
export function addColumn(columns, columnName) {
|
||||
if (columns.includes(columnName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
columns.push(columnName);
|
||||
}
|
||||
|
||||
export function removeColumn(columns, columnName) {
|
||||
if (!columns.includes(columnName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
columns.splice(columns.indexOf(columnName), 1);
|
||||
}
|
||||
|
||||
export function moveColumn(columns, columnName, newIndex) {
|
||||
if (newIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newIndex >= columns.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!columns.includes(columnName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
columns.splice(columns.indexOf(columnName), 1); // remove at old index
|
||||
columns.splice(newIndex, 0, columnName); // insert before new index
|
||||
}
|
|
@ -8,9 +8,27 @@
|
|||
{{name | shortDots}} <i ng-class="headerClass(name)" ng-click="cycleSortOrder(name)" tooltip="{{tooltip(name)}}" tooltip-append-to-body="1"></i>
|
||||
</span>
|
||||
<span class="table-header-move">
|
||||
<i ng-click="toggleColumn(name)" ng-show="canRemove(name)" class="fa fa-remove" tooltip="Remove column" tooltip-append-to-body="1"></i>
|
||||
<i ng-click="moveLeft(name)" class="fa fa-angle-double-left" ng-show="!$first" tooltip="Move column to the left" tooltip-append-to-body="1"></i>
|
||||
<i ng-click="moveRight(name)" class="fa fa-angle-double-right" ng-show="!$last" tooltip="Move column to the right" tooltip-append-to-body="1"></i>
|
||||
<i
|
||||
class="fa fa-remove"
|
||||
ng-click="onRemoveColumn(name)"
|
||||
ng-if="canRemoveColumn(name)"
|
||||
tooltip-append-to-body="1"
|
||||
tooltip="Remove column"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-angle-double-left"
|
||||
ng-click="moveColumnLeft(name)"
|
||||
ng-if="canMoveColumnLeft(name)"
|
||||
tooltip-append-to-body="1"
|
||||
tooltip="Move column to the left"
|
||||
></i>
|
||||
<i
|
||||
class="fa fa-angle-double-right"
|
||||
ng-click="moveColumnRight(name)"
|
||||
ng-if="canMoveColumnRight(name)"
|
||||
tooltip-append-to-body="1"
|
||||
tooltip="Move column to the right"
|
||||
></i>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
|
|
|
@ -13,6 +13,8 @@ module.directive('kbnTableHeader', function (shortDotsFilter) {
|
|||
sortOrder: '=',
|
||||
indexPattern: '=',
|
||||
onChangeSortOrder: '=?',
|
||||
onRemoveColumn: '=?',
|
||||
onMoveColumn: '=?',
|
||||
},
|
||||
template: headerHtml,
|
||||
controller: function ($scope) {
|
||||
|
@ -29,8 +31,25 @@ module.directive('kbnTableHeader', function (shortDotsFilter) {
|
|||
return 'Sort by ' + shortDotsFilter(column);
|
||||
};
|
||||
|
||||
$scope.canRemove = function (name) {
|
||||
return (name !== '_source' || $scope.columns.length !== 1);
|
||||
$scope.canMoveColumnLeft = function canMoveColumn(columnName) {
|
||||
return (
|
||||
_.isFunction($scope.onMoveColumn)
|
||||
&& $scope.columns.indexOf(columnName) > 0
|
||||
);
|
||||
};
|
||||
|
||||
$scope.canMoveColumnRight = function canMoveColumn(columnName) {
|
||||
return (
|
||||
_.isFunction($scope.onMoveColumn)
|
||||
&& $scope.columns.indexOf(columnName) < $scope.columns.length - 1
|
||||
);
|
||||
};
|
||||
|
||||
$scope.canRemoveColumn = function canRemoveColumn(columnName) {
|
||||
return (
|
||||
_.isFunction($scope.onRemoveColumn)
|
||||
&& (columnName !== '_source' || $scope.columns.length > 1)
|
||||
);
|
||||
};
|
||||
|
||||
$scope.headerClass = function (column) {
|
||||
|
@ -43,22 +62,24 @@ module.directive('kbnTableHeader', function (shortDotsFilter) {
|
|||
return ['fa', sortOrder[1] === 'asc' ? 'fa-sort-up' : 'fa-sort-down'];
|
||||
};
|
||||
|
||||
$scope.moveLeft = function (column) {
|
||||
let index = _.indexOf($scope.columns, column);
|
||||
if (index === 0) return;
|
||||
$scope.moveColumnLeft = function moveLeft(columnName) {
|
||||
const newIndex = $scope.columns.indexOf(columnName) - 1;
|
||||
|
||||
_.move($scope.columns, index, --index);
|
||||
if (newIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.onMoveColumn(columnName, newIndex);
|
||||
};
|
||||
|
||||
$scope.moveRight = function (column) {
|
||||
let index = _.indexOf($scope.columns, column);
|
||||
if (index === $scope.columns.length - 1) return;
|
||||
$scope.moveColumnRight = function moveRight(columnName) {
|
||||
const newIndex = $scope.columns.indexOf(columnName) + 1;
|
||||
|
||||
_.move($scope.columns, index, ++index);
|
||||
};
|
||||
if (newIndex >= $scope.columns.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.toggleColumn = function (fieldName) {
|
||||
_.toggleInOut($scope.columns, fieldName);
|
||||
$scope.onMoveColumn(columnName, newIndex);
|
||||
};
|
||||
|
||||
$scope.cycleSortOrder = function cycleSortOrder(columnName) {
|
||||
|
|
|
@ -36,7 +36,9 @@ module.directive('kbnTableRow', function ($compile, $httpParamSerializer, kbnUrl
|
|||
columns: '=',
|
||||
filter: '=',
|
||||
indexPattern: '=',
|
||||
row: '=kbnTableRow'
|
||||
row: '=kbnTableRow',
|
||||
onAddColumn: '=?',
|
||||
onRemoveColumn: '=?',
|
||||
},
|
||||
link: function ($scope, $el) {
|
||||
$el.after('<tr>');
|
||||
|
|
|
@ -16,5 +16,12 @@
|
|||
View single document
|
||||
</a>
|
||||
</div>
|
||||
<doc-viewer hit="row" filter="filter" columns="columns" index-pattern="indexPattern"></doc-viewer>
|
||||
<doc-viewer
|
||||
columns="columns"
|
||||
filter="filter"
|
||||
hit="row"
|
||||
index-pattern="indexPattern"
|
||||
on-add-column="onAddColumn"
|
||||
on-remove-column="onRemoveColumn"
|
||||
></doc-viewer>
|
||||
</td>
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
index-pattern="indexPattern"
|
||||
sort-order="sorting"
|
||||
on-change-sort-order="onChangeSortOrder"
|
||||
on-move-column="onMoveColumn"
|
||||
on-remove-column="onRemoveColumn"
|
||||
></thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="row in page|limitTo:limit track by row._index+row._type+row._id+row._score+row._version"
|
||||
|
@ -19,7 +21,9 @@
|
|||
index-pattern="indexPattern"
|
||||
filter="filter"
|
||||
class="discover-table-row"
|
||||
on-add-column="onAddColumn"
|
||||
on-change-sort-order="onChangeSortOrder"
|
||||
on-remove-column="onRemoveColumn"
|
||||
></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -32,6 +36,8 @@
|
|||
index-pattern="indexPattern"
|
||||
sort-order="sorting"
|
||||
on-change-sort-order="onChangeSortOrder"
|
||||
on-move-column="onMoveColumn"
|
||||
on-remove-column="onRemoveColumn"
|
||||
></thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="row in hits|limitTo:limit track by row._index+row._type+row._id+row._score+row._version"
|
||||
|
@ -43,7 +49,9 @@
|
|||
class="discover-table-row"
|
||||
ng-class="{'discover-table-row--highlight': row['$$_isAnchor']}"
|
||||
data-test-subj="docTableRow{{ row['$$_isAnchor'] ? ' docTableAnchorRow' : ''}}"
|
||||
on-add-column="onAddColumn"
|
||||
on-change-sort-order="onChangeSortOrder"
|
||||
on-remove-column="onRemoveColumn"
|
||||
></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -23,7 +23,10 @@ uiModules.get('kibana')
|
|||
searchSource: '=?',
|
||||
infiniteScroll: '=?',
|
||||
filter: '=?',
|
||||
onAddColumn: '=?',
|
||||
onChangeSortOrder: '=?',
|
||||
onMoveColumn: '=?',
|
||||
onRemoveColumn: '=?',
|
||||
},
|
||||
link: function ($scope) {
|
||||
const notify = new Notifier();
|
||||
|
|
|
@ -14,7 +14,9 @@ uiModules.get('kibana')
|
|||
hit: '=',
|
||||
indexPattern: '=',
|
||||
filter: '=?',
|
||||
columns: '=?'
|
||||
columns: '=?',
|
||||
onAddColumn: '=?',
|
||||
onRemoveColumn: '=?',
|
||||
},
|
||||
template: function ($el) {
|
||||
const $viewer = $('<div class="doc-viewer">');
|
||||
|
@ -28,7 +30,14 @@ uiModules.get('kibana')
|
|||
<a ng-click="mode='${view.name}'">${view.title}</a>
|
||||
</li>`);
|
||||
$tabs.append($tab);
|
||||
const $viewAttrs = 'hit="hit" index-pattern="indexPattern" filter="filter" columns="columns"';
|
||||
const $viewAttrs = `
|
||||
hit="hit"
|
||||
index-pattern="indexPattern"
|
||||
filter="filter"
|
||||
columns="columns"
|
||||
on-add-column="onAddColumn"
|
||||
on-remove-column="onRemoveColumn"
|
||||
`;
|
||||
const $ext = $(`<render-directive ${$viewAttrs} ng-if="mode == '${view.name}'" definition="docViews['${view.name}'].directive">
|
||||
</render-directive>`);
|
||||
$ext.html(view.directive.template);
|
||||
|
|
|
@ -261,8 +261,7 @@ export default class DiscoverPage {
|
|||
}
|
||||
|
||||
clickFieldListItem(field) {
|
||||
return this.findTimeout
|
||||
.findByCssSelector('li[attr-field="' + field + '"]').click();
|
||||
return PageObjects.common.clickTestSubject(`field-${field}`);
|
||||
}
|
||||
|
||||
async clickFieldListItemAdd(field) {
|
||||
|
@ -296,6 +295,7 @@ export default class DiscoverPage {
|
|||
async removeAllFilters() {
|
||||
await PageObjects.common.clickTestSubject('showFilterActions');
|
||||
await PageObjects.common.clickTestSubject('removeAllFilters');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue