mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[7.x] Adds capability to show percentages for data table columns (#41206)
* Bring table vis params styles inline with others * Add percentage column option to table vis
This commit is contained in:
parent
85dfc42aa8
commit
25d25ace42
14 changed files with 461 additions and 139 deletions
|
@ -27,6 +27,7 @@ import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy';
|
|||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import { VisProvider } from 'ui/vis';
|
||||
import { tabifyAggResponse } from 'ui/agg_response/tabify';
|
||||
import { round } from 'lodash';
|
||||
|
||||
describe('AggTable Directive', function () {
|
||||
|
||||
|
@ -211,7 +212,7 @@ describe('AggTable Directive', function () {
|
|||
expect($cells.length).to.be(6);
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
expect($($cells[i]).text()).to.be(expected[i]);
|
||||
expect($($cells[i]).text().trim()).to.be(expected[i]);
|
||||
}
|
||||
settings.set('dateFormat:tz', oldTimezoneSetting);
|
||||
off();
|
||||
|
@ -353,6 +354,60 @@ describe('AggTable Directive', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('renders percentage columns', async function () {
|
||||
$scope.dimensions = {
|
||||
buckets: [
|
||||
{ accessor: 0, params: {} },
|
||||
{ accessor: 1, format: { id: 'date', params: { pattern: 'YYYY-MM-DD' } } },
|
||||
],
|
||||
metrics: [
|
||||
{ accessor: 2, format: { id: 'number' } },
|
||||
{ accessor: 3, format: { id: 'date' } },
|
||||
{ accessor: 4, format: { id: 'number' } },
|
||||
{ accessor: 5, format: { id: 'number' } },
|
||||
],
|
||||
};
|
||||
const response = await tableAggResponse(
|
||||
tabifiedData.oneTermOneHistogramBucketWithTwoMetricsOneTopHitOneDerivative,
|
||||
$scope.dimensions
|
||||
);
|
||||
$scope.table = response.tables[0];
|
||||
$scope.percentageCol = 'Average bytes';
|
||||
|
||||
const $el = $(`<kbn-agg-table
|
||||
table="table"
|
||||
dimensions="dimensions"
|
||||
percentage-col="percentageCol"
|
||||
></kbn-agg-table>`);
|
||||
|
||||
$compile($el)($scope);
|
||||
$scope.$digest();
|
||||
|
||||
const $headings = $el.find('th');
|
||||
expect($headings.length).to.be(7);
|
||||
expect(
|
||||
$headings
|
||||
.eq(3)
|
||||
.text()
|
||||
.trim()
|
||||
).to.be('Average bytes percentages');
|
||||
|
||||
const countColId = $scope.table.columns.find(col => col.name === $scope.percentageCol).id;
|
||||
const counts = $scope.table.rows.map(row => row[countColId]);
|
||||
const total = counts.reduce((sum, curr) => sum + curr, 0);
|
||||
const $percentageColValues = $el.find('tbody tr').map((i, el) =>
|
||||
$(el)
|
||||
.find('td')
|
||||
.eq(3)
|
||||
.text()
|
||||
);
|
||||
|
||||
$percentageColValues.each((i, value) => {
|
||||
const percentage = `${round((counts[i] / total) * 100, 1)}%`;
|
||||
expect(value).to.be(percentage);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aggTable.exportAsCsv()', function () {
|
||||
let origBlob;
|
||||
function FakeBlob(slices, opts) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
per-page="perPage"
|
||||
sort="sort"
|
||||
show-total="showTotal"
|
||||
percentage-col="percentageCol"
|
||||
filter="filter"
|
||||
totalFunc="totalFunc">
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import _ from 'lodash';
|
|||
import { uiModules } from 'ui/modules';
|
||||
import aggTableTemplate from './agg_table.html';
|
||||
import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
uiModules
|
||||
.get('kibana', ['RecursionHelper'])
|
||||
|
@ -40,6 +41,7 @@ uiModules
|
|||
exportTitle: '=?',
|
||||
showTotal: '=',
|
||||
totalFunc: '=',
|
||||
percentageCol: '=',
|
||||
filter: '=',
|
||||
},
|
||||
controllerAs: 'aggTable',
|
||||
|
@ -94,78 +96,177 @@ uiModules
|
|||
}).join('');
|
||||
};
|
||||
|
||||
$scope.$watch('table', function () {
|
||||
const table = $scope.table;
|
||||
$scope.$watchMulti(
|
||||
['table', 'exportTitle', 'percentageCol', 'totalFunc', '=scope.dimensions'],
|
||||
function () {
|
||||
const { table, exportTitle, percentageCol } = $scope;
|
||||
const showPercentage = percentageCol !== '';
|
||||
|
||||
if (!table) {
|
||||
$scope.rows = null;
|
||||
$scope.formattedColumns = null;
|
||||
return;
|
||||
if (!table) {
|
||||
$scope.rows = null;
|
||||
$scope.formattedColumns = null;
|
||||
return;
|
||||
}
|
||||
|
||||
self.csv.filename = (exportTitle || table.title || 'table') + '.csv';
|
||||
$scope.rows = table.rows;
|
||||
$scope.formattedColumns = [];
|
||||
|
||||
if (typeof $scope.dimensions === 'undefined') return;
|
||||
|
||||
const { buckets, metrics } = $scope.dimensions;
|
||||
|
||||
$scope.formattedColumns = table.columns
|
||||
.map(function (col, i) {
|
||||
const isBucket = buckets.find(bucket => bucket.accessor === i);
|
||||
const dimension = isBucket || metrics.find(metric => metric.accessor === i);
|
||||
|
||||
if (!dimension) return;
|
||||
|
||||
const formatter = getFormat(dimension.format);
|
||||
|
||||
const formattedColumn = {
|
||||
id: col.id,
|
||||
title: col.name,
|
||||
formatter: formatter,
|
||||
filterable: !!isBucket,
|
||||
};
|
||||
|
||||
const last = i === table.columns.length - 1;
|
||||
|
||||
if (last || !isBucket) {
|
||||
formattedColumn.class = 'visualize-table-right';
|
||||
}
|
||||
|
||||
const isDate =
|
||||
_.get(dimension, 'format.id') === 'date' ||
|
||||
_.get(dimension, 'format.params.id') === 'date';
|
||||
const isNumeric =
|
||||
_.get(dimension, 'format.id') === 'number' ||
|
||||
_.get(dimension, 'format.params.id') === 'number';
|
||||
|
||||
let { totalFunc } = $scope;
|
||||
if (typeof totalFunc === 'undefined' && showPercentage) {
|
||||
totalFunc = 'sum';
|
||||
}
|
||||
|
||||
if (isNumeric || isDate || totalFunc === 'count') {
|
||||
const sum = tableRows => {
|
||||
return _.reduce(
|
||||
tableRows,
|
||||
function (prev, curr) {
|
||||
// some metrics return undefined for some of the values
|
||||
// derivative is an example of this as it returns undefined in the first row
|
||||
if (curr[col.id] === undefined) return prev;
|
||||
return prev + curr[col.id];
|
||||
},
|
||||
0
|
||||
);
|
||||
};
|
||||
|
||||
formattedColumn.sumTotal = sum(table.rows);
|
||||
|
||||
switch (totalFunc) {
|
||||
case 'sum': {
|
||||
if (!isDate) {
|
||||
const total = formattedColumn.sumTotal;
|
||||
formattedColumn.formattedTotal = formatter.convert(total);
|
||||
formattedColumn.total = formattedColumn.sumTotal;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'avg': {
|
||||
if (!isDate) {
|
||||
const total = sum(table.rows) / table.rows.length;
|
||||
formattedColumn.formattedTotal = formatter.convert(total);
|
||||
formattedColumn.total = total;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'min': {
|
||||
const total = _.chain(table.rows)
|
||||
.map(col.id)
|
||||
.min()
|
||||
.value();
|
||||
formattedColumn.formattedTotal = formatter.convert(total);
|
||||
formattedColumn.total = total;
|
||||
break;
|
||||
}
|
||||
case 'max': {
|
||||
const total = _.chain(table.rows)
|
||||
.map(col.id)
|
||||
.max()
|
||||
.value();
|
||||
formattedColumn.formattedTotal = formatter.convert(total);
|
||||
formattedColumn.total = total;
|
||||
break;
|
||||
}
|
||||
case 'count': {
|
||||
const total = table.rows.length;
|
||||
formattedColumn.formattedTotal = total;
|
||||
formattedColumn.total = total;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return formattedColumn;
|
||||
})
|
||||
.filter(column => column);
|
||||
|
||||
if (showPercentage) {
|
||||
const insertAtIndex = _.findIndex($scope.formattedColumns, { title: percentageCol });
|
||||
|
||||
// column to show percentage for was removed
|
||||
if (insertAtIndex < 0) return;
|
||||
|
||||
const { cols, rows } = addPercentageCol(
|
||||
$scope.formattedColumns,
|
||||
percentageCol,
|
||||
table.rows,
|
||||
insertAtIndex
|
||||
);
|
||||
$scope.rows = rows;
|
||||
$scope.formattedColumns = cols;
|
||||
}
|
||||
}
|
||||
|
||||
self.csv.filename = ($scope.exportTitle || table.title || 'table') + '.csv';
|
||||
$scope.rows = table.rows;
|
||||
$scope.formattedColumns = table.columns.map(function (col, i) {
|
||||
const isBucket = $scope.dimensions.buckets.find(bucket => bucket.accessor === i);
|
||||
const dimension = isBucket || $scope.dimensions.metrics.find(metric => metric.accessor === i);
|
||||
if (!dimension) return;
|
||||
|
||||
const formatter = getFormat(dimension.format);
|
||||
|
||||
const formattedColumn = {
|
||||
id: col.id,
|
||||
title: col.name,
|
||||
formatter: formatter,
|
||||
filterable: !!isBucket
|
||||
};
|
||||
|
||||
const last = i === (table.columns.length - 1);
|
||||
|
||||
if (last || !isBucket) {
|
||||
formattedColumn.class = 'visualize-table-right';
|
||||
}
|
||||
|
||||
const isDate = _.get(dimension, 'format.id') === 'date' || _.get(dimension, 'format.params.id') === 'date';
|
||||
const isNumeric = _.get(dimension, 'format.id') === 'number' || _.get(dimension, 'format.params.id') === 'number';
|
||||
|
||||
if (isNumeric || isDate || $scope.totalFunc === 'count') {
|
||||
const sum = tableRows => {
|
||||
return _.reduce(tableRows, function (prev, curr) {
|
||||
// some metrics return undefined for some of the values
|
||||
// derivative is an example of this as it returns undefined in the first row
|
||||
if (curr[col.id] === undefined) return prev;
|
||||
return prev + curr[col.id];
|
||||
}, 0);
|
||||
};
|
||||
|
||||
switch ($scope.totalFunc) {
|
||||
case 'sum':
|
||||
if (!isDate) {
|
||||
formattedColumn.total = formatter.convert(sum(table.rows));
|
||||
}
|
||||
break;
|
||||
case 'avg':
|
||||
if (!isDate) {
|
||||
formattedColumn.total = formatter.convert(sum(table.rows) / table.rows.length);
|
||||
}
|
||||
break;
|
||||
case 'min':
|
||||
formattedColumn.total = formatter.convert(_.chain(table.rows).map(col.id).min().value());
|
||||
break;
|
||||
case 'max':
|
||||
formattedColumn.total = formatter.convert(_.chain(table.rows).map(col.id).max().value());
|
||||
break;
|
||||
case 'count':
|
||||
formattedColumn.total = table.rows.length;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return formattedColumn;
|
||||
}).filter(column => column);
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {[]Object} columns - the formatted columns that will be displayed
|
||||
* @param {String} title - the title of the column to add to
|
||||
* @param {[]Object} rows - the row data for the columns
|
||||
* @param {Number} insertAtIndex - the index to insert the percentage column at
|
||||
* @returns {Object} - cols and rows for the table to render now included percentage column(s)
|
||||
*/
|
||||
function addPercentageCol(columns, title, rows, insertAtIndex) {
|
||||
const { id, sumTotal } = columns[insertAtIndex];
|
||||
const newId = `${id}-percents`;
|
||||
const formatter = getFormat({ id: 'percent' });
|
||||
const i18nTitle = i18n.translate('tableVis.params.percentageTableColumnName', {
|
||||
defaultMessage: '{title} percentages',
|
||||
values: { title },
|
||||
});
|
||||
const newCols = insert(columns, insertAtIndex, {
|
||||
title: i18nTitle,
|
||||
id: newId,
|
||||
formatter,
|
||||
});
|
||||
const newRows = rows.map(row => ({
|
||||
[newId]: formatter.convert(row[id] / sumTotal / 100),
|
||||
...row,
|
||||
}));
|
||||
|
||||
return { cols: newCols, rows: newRows };
|
||||
}
|
||||
|
||||
function insert(arr, index, ...items) {
|
||||
const newArray = [...arr];
|
||||
newArray.splice(index + 1, 0, ...items);
|
||||
return newArray;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
dimensions="dimensions"
|
||||
per-page="perPage"
|
||||
sort="sort"
|
||||
percentage-col="percentageCol"
|
||||
show-total="showTotal"
|
||||
total-func="totalFunc"></kbn-agg-table-group>
|
||||
<kbn-agg-table
|
||||
|
@ -26,6 +27,7 @@
|
|||
export-title="exportTitle"
|
||||
per-page="perPage"
|
||||
sort="sort"
|
||||
percentage-col="percentageCol"
|
||||
show-total="showTotal"
|
||||
total-func="totalFunc">
|
||||
</kbn-agg-table>
|
||||
|
@ -53,6 +55,7 @@
|
|||
per-page="perPage"
|
||||
sort="sort"
|
||||
show-total="showTotal"
|
||||
percentage-col="percentageCol"
|
||||
total-func="totalFunc"></kbn-agg-table-group>
|
||||
<kbn-agg-table
|
||||
ng-if="table.rows"
|
||||
|
@ -63,6 +66,7 @@
|
|||
per-page="perPage"
|
||||
sort="sort"
|
||||
show-total="showTotal"
|
||||
percentage-col="percentageCol"
|
||||
total-func="totalFunc">
|
||||
</kbn-agg-table>
|
||||
</td>
|
||||
|
|
|
@ -37,6 +37,7 @@ uiModules
|
|||
exportTitle: '=?',
|
||||
showTotal: '=',
|
||||
totalFunc: '=',
|
||||
percentageCol: '=',
|
||||
filter: '=',
|
||||
},
|
||||
compile: function ($el) {
|
||||
|
|
|
@ -41,7 +41,9 @@
|
|||
</tbody>
|
||||
<tfoot ng-if="showTotal">
|
||||
<tr>
|
||||
<th scope="col" ng-repeat="col in columns" class="numeric-value">{{col.total}}</th>
|
||||
<th scope="col" ng-repeat="col in columns" class="numeric-value">
|
||||
{{ col.formattedTotal }}
|
||||
</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
|
|
@ -45,6 +45,7 @@ uiModules
|
|||
showTotal: '=',
|
||||
totalFunc: '=',
|
||||
filter: '=',
|
||||
percentageCol: '=',
|
||||
},
|
||||
controllerAs: 'paginatedTable',
|
||||
controller: function ($scope) {
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
export-title="visState.title"
|
||||
per-page="visState.params.perPage"
|
||||
sort="sort"
|
||||
percentage-col="visState.params.percentageCol"
|
||||
show-total="visState.params.showTotal"
|
||||
total-func="visState.params.totalFunc">
|
||||
</kbn-agg-table-group>
|
||||
|
|
|
@ -70,7 +70,8 @@ function TableVisTypeProvider(Private) {
|
|||
direction: null
|
||||
},
|
||||
showTotal: false,
|
||||
totalFunc: 'sum'
|
||||
totalFunc: 'sum',
|
||||
percentageCol: '',
|
||||
},
|
||||
template: tableVisTemplate,
|
||||
},
|
||||
|
|
|
@ -1,61 +1,115 @@
|
|||
|
||||
<div class="visEditorSidebar__section">
|
||||
<div class="form-group">
|
||||
<div class="visEditorSidebar__sectionTitle">
|
||||
<div
|
||||
i18n-id="tableVis.params.showMetricsLabel.optionsTitle"
|
||||
i18n-default-message="Options"
|
||||
></div>
|
||||
</div>
|
||||
<div class="visEditorSidebar__formRow">
|
||||
<label
|
||||
class="visEditorSidebar__formLabel"
|
||||
for="datatableVisualizationPerPage"
|
||||
i18n-id="tableVis.params.perPageLabel"
|
||||
i18n-default-message="Per Page"
|
||||
></label>
|
||||
<input type="number" ng-model="editorState.params.perPage" class="form-control" id="datatableVisualizationPerPage">
|
||||
<div class="visEditorSidebar__formControl">
|
||||
<input
|
||||
class="kuiInput visEditorSidebar__input"
|
||||
id="datatableVisualizationPerPage"
|
||||
type="number"
|
||||
ng-model="editorState.params.perPage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="editorState.params.showMetricsAtAllLevels" data-test-subj="showMetricsAtAllLevels">
|
||||
<span
|
||||
i18n-id="tableVis.params.showMetricsLabel"
|
||||
i18n-default-message="Show metrics for every bucket/level"
|
||||
></span>
|
||||
</label>
|
||||
<div class="visEditorSidebar__formRow">
|
||||
<label
|
||||
class="visEditorSidebar__formLabel"
|
||||
for="showMetricsAtAllLevelsCheckbox"
|
||||
i18n-id="tableVis.params.showMetricsLabel"
|
||||
i18n-default-message="Show metrics for every bucket/level"
|
||||
></label>
|
||||
<div class="visEditorSidebar__formControl">
|
||||
<input
|
||||
class="kuiCheckBox"
|
||||
id="showMetricsAtAllLevelsCheckbox"
|
||||
type="checkbox"
|
||||
ng-model="editorState.params.showMetricsAtAllLevels"
|
||||
data-test-subj="showMetricsAtAllLevels"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="editorState.params.showPartialRows" data-test-subj="showPartialRows">
|
||||
<span
|
||||
<div class="visEditorSidebar__formRow">
|
||||
<div class="visEditorSidebar__formLabel">
|
||||
<label
|
||||
for="showPartialRowsCheckbox"
|
||||
i18n-id="tableVis.params.showPartialRowsLabel"
|
||||
i18n-default-message="Show partial rows"
|
||||
></span>
|
||||
|
||||
>
|
||||
</label>
|
||||
<icon-tip
|
||||
content="::'tableVis.params.showPartialRowsTip' | i18n: {
|
||||
defaultMessage: 'Show rows that have partial data. This will still calculate metrics for every bucket/level, even if they are not displayed.'
|
||||
}"
|
||||
position="'right'"
|
||||
></icon-tip>
|
||||
</label>
|
||||
</div>
|
||||
<div class="visEditorSidebar__formControl">
|
||||
<input
|
||||
class="kuiCheckBox"
|
||||
id="showPartialRowsCheckbox"
|
||||
type="checkbox"
|
||||
ng-model="editorState.params.showPartialRows"
|
||||
data-test-subj="showPartialRows"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="editorState.params.showTotal">
|
||||
<span
|
||||
i18n-id="tableVis.params.showTotalLabel"
|
||||
i18n-default-message="Show total"
|
||||
></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="visEditorSidebar__formRow">
|
||||
<label
|
||||
class="visEditorSidebar__formLabel"
|
||||
for="showTotalCheckbox"
|
||||
i18n-id="tableVis.params.showTotalLabel"
|
||||
i18n-default-message="Show total"
|
||||
></label>
|
||||
<div class="visEditorSidebar__formControl">
|
||||
<input
|
||||
class="kuiCheckBox"
|
||||
id="showTotalCheckbox"
|
||||
type="checkbox"
|
||||
ng-model="editorState.params.showTotal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="visEditorSidebar__formRow">
|
||||
<label
|
||||
class="visEditorSidebar__formLabel"
|
||||
for="datatableVisualizationTotalFunction"
|
||||
i18n-id="tableVis.params.totalFunctionLabel"
|
||||
i18n-default-message="Total function"
|
||||
></label>
|
||||
<select ng-disabled="!editorState.params.showTotal"
|
||||
class="form-control"
|
||||
ng-model="editorState.params.totalFunc"
|
||||
ng-options="x for x in totalAggregations" id="datatableVisualizationTotalFunction">
|
||||
</select>
|
||||
<div class="visEditorSidebar__formControl">
|
||||
<select
|
||||
id="datatableVisualizationTotalFunction"
|
||||
ng-disabled="!editorState.params.showTotal"
|
||||
class="kuiSelect visEditorSidebar__select"
|
||||
ng-model="editorState.params.totalFunc"
|
||||
ng-options="x for x in totalAggregations"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="visEditorSidebar__formRow">
|
||||
<label
|
||||
class="visEditorSidebar__formLabel"
|
||||
for="datatableVisualizationPercentageCol"
|
||||
i18n-id="tableVis.params.PercentageColLabel"
|
||||
i18n-default-message="Percentage column"
|
||||
></label>
|
||||
<div class="visEditorSidebar__formControl">
|
||||
<select
|
||||
id="datatableVisualizationPercentageCol"
|
||||
data-test-subj="percentageCol"
|
||||
class="kuiSelect visEditorSidebar__select"
|
||||
ng-model="editorState.params.percentageCol"
|
||||
ng-options="col.value as col.name for col in percentageColumns"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -18,29 +18,67 @@
|
|||
*/
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { tabifyGetColumns } from 'ui/agg_response/tabify/_get_columns.js';
|
||||
import tableVisParamsTemplate from './table_vis_params.html';
|
||||
import _ from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
uiModules.get('kibana/table_vis')
|
||||
.directive('tableVisParams', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: tableVisParamsTemplate,
|
||||
link: function ($scope) {
|
||||
$scope.totalAggregations = ['sum', 'avg', 'min', 'max', 'count'];
|
||||
uiModules.get('kibana/table_vis').directive('tableVisParams', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: tableVisParamsTemplate,
|
||||
link: function ($scope) {
|
||||
const noCol = {
|
||||
value: '',
|
||||
name: i18n.translate('tableVis.params.defaultPercetangeCol', {
|
||||
defaultMessage: 'Don’t show',
|
||||
})
|
||||
};
|
||||
$scope.totalAggregations = ['sum', 'avg', 'min', 'max', 'count'];
|
||||
$scope.percentageColumns = [noCol];
|
||||
|
||||
$scope.$watchMulti([
|
||||
'editorState.params.showPartialRows',
|
||||
'editorState.params.showMetricsAtAllLevels'
|
||||
], function () {
|
||||
$scope.$watchMulti([
|
||||
'[]editorState.aggs',
|
||||
'editorState.params.percentageCol',
|
||||
'=editorState.params.dimensions.buckets',
|
||||
'=editorState.params.dimensions.metrics',
|
||||
'vis.dirty' // though not used directly in the callback, it is a strong indicator that we should recompute
|
||||
], function () {
|
||||
const { aggs, params } = $scope.editorState;
|
||||
|
||||
$scope.percentageColumns = [noCol, ...tabifyGetColumns(aggs.getResponseAggs(), true)
|
||||
.filter(col => isNumeric(_.get(col, 'aggConfig.type.name'), params.dimensions))
|
||||
.map(col => ({ value: col.name, name: col.name }))];
|
||||
|
||||
if (!_.find($scope.percentageColumns, { value: params.percentageCol })) {
|
||||
params.percentageCol = $scope.percentageColumns[0].value;
|
||||
}
|
||||
}, true);
|
||||
|
||||
$scope.$watchMulti(
|
||||
['editorState.params.showPartialRows', 'editorState.params.showMetricsAtAllLevels'],
|
||||
function () {
|
||||
if (!$scope.vis) return;
|
||||
|
||||
const params = $scope.editorState.params;
|
||||
if (params.showPartialRows || params.showMetricsAtAllLevels) {
|
||||
$scope.metricsAtAllLevels = true;
|
||||
} else {
|
||||
$scope.metricsAtAllLevels = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
$scope.metricsAtAllLevels = params.showPartialRows || params.showMetricsAtAllLevels;
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Determines if a aggConfig is numeric
|
||||
* @param {String} type - the type of the aggConfig
|
||||
* @param {Object} obj - dimensions of the current visualization or editor
|
||||
* @param {Object} obj.buckets
|
||||
* @param {Object} obj.metrics
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isNumeric(type, { buckets = [], metrics = [] } = {}) {
|
||||
const dimension =
|
||||
buckets.find(({ aggType }) => aggType === type) ||
|
||||
metrics.find(({ aggType }) => aggType === type);
|
||||
const formatType = _.get(dimension, 'format.id') || _.get(dimension, 'format.params.id');
|
||||
return formatType === 'number';
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
|
||||
<!-- remove button -->
|
||||
<button
|
||||
data-test-subj="removeDimensionBtn"
|
||||
ng-if="canRemove(agg)"
|
||||
aria-label="{{::'common.ui.vis.editors.agg.removeDimensionButtonAriaLabel' | i18n: { defaultMessage: 'Remove Dimension' } }}"
|
||||
ng-if="stats.count > stats.min"
|
||||
|
|
|
@ -97,6 +97,62 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should show percentage columns', async () => {
|
||||
async function expectValidTableData() {
|
||||
const data = await PageObjects.visualize.getTableVisData();
|
||||
expect(data.trim().split('\n')).to.be.eql([
|
||||
'0 to 1000',
|
||||
'1,351 64.7%',
|
||||
'1000 to 2000',
|
||||
'737 35.3%',
|
||||
]);
|
||||
}
|
||||
|
||||
// load a plain table
|
||||
await PageObjects.visualize.navigateToNewVisualization();
|
||||
await PageObjects.visualize.clickDataTable();
|
||||
await PageObjects.visualize.clickNewSearch();
|
||||
await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime);
|
||||
await PageObjects.visualize.clickBucket('Split rows');
|
||||
await PageObjects.visualize.selectAggregation('Range');
|
||||
await PageObjects.visualize.selectField('bytes');
|
||||
await PageObjects.visualize.clickGo();
|
||||
await PageObjects.visualize.clickOptionsTab();
|
||||
await PageObjects.visualize.setSelectByOptionText(
|
||||
'datatableVisualizationPercentageCol',
|
||||
'Count'
|
||||
);
|
||||
await PageObjects.visualize.clickGo();
|
||||
|
||||
await expectValidTableData();
|
||||
|
||||
// check that it works after a save and reload
|
||||
const SAVE_NAME = 'viz w/ percents';
|
||||
await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(SAVE_NAME);
|
||||
await PageObjects.visualize.waitForVisualizationSavedToastGone();
|
||||
await PageObjects.visualize.loadSavedVisualization(SAVE_NAME);
|
||||
await PageObjects.visualize.waitForVisualization();
|
||||
|
||||
await expectValidTableData();
|
||||
|
||||
// check that it works after selecting a column that's deleted
|
||||
await PageObjects.visualize.clickData();
|
||||
await PageObjects.visualize.clickBucket('Metric', 'metrics');
|
||||
await PageObjects.visualize.selectAggregation('Average', 'metrics');
|
||||
await PageObjects.visualize.selectField('bytes', 'metrics');
|
||||
await PageObjects.visualize.removeDimension(1);
|
||||
await PageObjects.visualize.clickGo();
|
||||
await PageObjects.visualize.clickOptionsTab();
|
||||
|
||||
const data = await PageObjects.visualize.getTableVisData();
|
||||
expect(data.trim().split('\n')).to.be.eql([
|
||||
'0 to 1000',
|
||||
'344.094B',
|
||||
'1000 to 2000',
|
||||
'1.697KB',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should show correct data when using average pipeline aggregation', async () => {
|
||||
await PageObjects.visualize.navigateToNewVisualization();
|
||||
await PageObjects.visualize.clickDataTable();
|
||||
|
|
|
@ -332,19 +332,22 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
|
|||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
}
|
||||
|
||||
async isChecked(selector) {
|
||||
const checkbox = await testSubjects.find(selector);
|
||||
return await checkbox.isSelected();
|
||||
}
|
||||
|
||||
async checkCheckbox(selector) {
|
||||
const element = await testSubjects.find(selector);
|
||||
const isSelected = await element.isSelected();
|
||||
if(!isSelected) {
|
||||
const isChecked = await this.isChecked(selector);
|
||||
if (!isChecked) {
|
||||
log.debug(`checking checkbox ${selector}`);
|
||||
await testSubjects.click(selector);
|
||||
}
|
||||
}
|
||||
|
||||
async uncheckCheckbox(selector) {
|
||||
const element = await testSubjects.find(selector);
|
||||
const isSelected = await element.isSelected();
|
||||
if(isSelected) {
|
||||
const isChecked = await this.isChecked(selector);
|
||||
if (isChecked) {
|
||||
log.debug(`unchecking checkbox ${selector}`);
|
||||
await testSubjects.click(selector);
|
||||
}
|
||||
|
@ -1255,6 +1258,9 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
|
|||
return result;
|
||||
}
|
||||
|
||||
async removeDimension(agg) {
|
||||
await testSubjects.click(`aggregationEditor${agg} removeDimensionBtn`);
|
||||
}
|
||||
}
|
||||
|
||||
return new VisualizePage();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue