diff --git a/src/ui/public/agg_response/tabify/tabify.js b/src/ui/public/agg_response/tabify/tabify.js
index d2d907a422e7c..f146a89da8068 100644
--- a/src/ui/public/agg_response/tabify/tabify.js
+++ b/src/ui/public/agg_response/tabify/tabify.js
@@ -30,10 +30,36 @@ export function AggResponseTabifyProvider(Private, Notifier) {
*/
function collectBucket(write, bucket, key) {
const agg = write.aggStack.shift();
+ const subAggPrefix = 'agg_';
switch (agg.schema.group) {
case 'buckets':
- const buckets = new Buckets(bucket[agg.id]);
+ let buckets = new Buckets(bucket[agg.id]);
+
+ // traverse the agg object
+ const bucketKeys = Object.keys(bucket);
+ const bucketLength = bucketKeys.length;
+ const subAggRegex = new RegExp('(' + subAggPrefix + ')*' + agg.id, 'g');
+ let subAggCnt = 0;
+ for (let i = 0; i < bucketLength; ++i) {
+ if (bucketKeys[i].match(subAggRegex)) {
+ const subAggPrefixRegex = new RegExp(subAggPrefix, 'g');
+ // get number of sub agg
+ subAggCnt = (bucketKeys[i].match(subAggPrefixRegex) || []).length;
+ break;
+ }
+ }
+ let aggBucket = bucket;
+ while (subAggCnt > 0) {
+ const prefix = subAggPrefix.repeat(subAggCnt) + agg.id;
+ aggBucket = aggBucket[prefix];
+ subAggCnt--;
+ }
+ // get the deepest agg result
+ if (aggBucket !== undefined) {
+ buckets = new Buckets(aggBucket[agg.id]);
+ }
+
if (buckets.length) {
const splitting = write.canSplit && agg.schema.name === 'split';
if (splitting) {
diff --git a/src/ui/public/agg_types/buckets/_bucket_agg_type.js b/src/ui/public/agg_types/buckets/_bucket_agg_type.js
index 44762ec86d876..00f572854c799 100644
--- a/src/ui/public/agg_types/buckets/_bucket_agg_type.js
+++ b/src/ui/public/agg_types/buckets/_bucket_agg_type.js
@@ -1,11 +1,35 @@
import _ from 'lodash';
import { AggTypesAggTypeProvider } from 'ui/agg_types/agg_type';
+import nestedAndChildTemplate from 'ui/agg_types/controls/nested_and_child.html';
export function AggTypesBucketsBucketAggTypeProvider(Private) {
const AggType = Private(AggTypesAggTypeProvider);
_.class(BucketAggType).inherits(AggType);
function BucketAggType(config) {
+ // always append reversed nested, child, nested
+ config.params.push(
+ {
+ name: 'reversedNested',
+ default: false,
+ write: _.noop
+ },
+ {
+ name: 'child',
+ default: [ { input: '' } ],
+ write: _.noop
+ },
+ {
+ name: 'nested',
+ default: 0,
+ write: _.noop
+ },
+ {
+ name: 'aggNestedAndChild',
+ editor: nestedAndChildTemplate
+ }
+ );
+
BucketAggType.Super.call(this, config);
if (_.isFunction(config.getKey)) {
diff --git a/src/ui/public/agg_types/controls/nested_and_child.html b/src/ui/public/agg_types/controls/nested_and_child.html
new file mode 100644
index 0000000000000..1dab046d5a454
--- /dev/null
+++ b/src/ui/public/agg_types/controls/nested_and_child.html
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+ Required: You must specify at least one child.
+
+
+
+
+
+
+
diff --git a/src/ui/public/vis/agg_configs.js b/src/ui/public/vis/agg_configs.js
index dac84c36ae142..dd89fce9835de 100644
--- a/src/ui/public/vis/agg_configs.js
+++ b/src/ui/public/vis/agg_configs.js
@@ -94,6 +94,8 @@ export function VisAggConfigsProvider(Private) {
const dslTopLvl = {};
let dslLvlCursor;
let nestedMetrics;
+ const subAggPrefix = 'agg_';
+ let prevSubAggCnt; // count of additional aggregation
if (this.vis.isHierarchical()) {
// collect all metrics, and filter out the ones that we won't be copying
@@ -119,7 +121,19 @@ export function VisAggConfigsProvider(Private) {
dslLvlCursor = dslTopLvl;
} else {
const prevConfig = list[i - 1];
- const prevDsl = dslLvlCursor[prevConfig.id];
+ let prevConfigKey;
+ let prevDsl;
+ if (prevSubAggCnt > 0) {
+ prevDsl = dslLvlCursor;
+ for (let i = prevSubAggCnt; i > 0; --i) {
+ prevConfigKey = subAggPrefix.repeat(i) + prevConfig.id; // previous config key: prefix * n + id
+ prevDsl = prevDsl[prevConfigKey].aggs;
+ }
+ prevDsl = prevDsl[prevConfig.id];
+ }
+ else{
+ prevDsl = dslLvlCursor[prevConfig.id];
+ }
// advance the cursor and nest under the previous agg, or
// put it on the same level if the previous agg doesn't accept
@@ -127,11 +141,66 @@ export function VisAggConfigsProvider(Private) {
dslLvlCursor = prevDsl.aggs || dslLvlCursor;
}
- const dsl = dslLvlCursor[config.id] = config.toDsl();
+ const dsl = config.toDsl();
let subAggs;
parseParentAggs(dslLvlCursor, dsl);
+ let newConfigId = config.id;
+ let newDsl = dsl; // define new dsl for additional aggregation
+ let aggObj = {}; // define aggObj to store orginal dsl
+ prevSubAggCnt = 0;
+ if (config.params.nested) {
+ const nestedArr = config.params.field.name.split('.');
+ const nestedLvl = (nestedArr.length - 1 < config.params.nested) ? nestedArr.length - 1 : config.params.nested;
+ for (let i = 0; i < nestedLvl; ++i) {
+ aggObj = {};
+ aggObj[newConfigId] = newDsl;
+ newDsl = {
+ nested: {
+ path: nestedArr[i]
+ },
+ aggs: aggObj
+ };
+ newConfigId = subAggPrefix + newConfigId;
+ prevSubAggCnt++;
+ }
+ }
+
+ if (config.params.child) {
+ const childArr = config.params.child;
+ const childCnt = childArr.length;
+ for (let i = 0; i < childCnt; ++i) {
+ const childInput = childArr[childCnt - i - 1].input;
+ if (!childInput) {
+ break;
+ }
+ aggObj = {};
+ aggObj[newConfigId] = newDsl;
+ newDsl = {
+ children: {
+ type: childInput
+ },
+ aggs: aggObj
+ };
+ newConfigId = subAggPrefix + newConfigId;
+ prevSubAggCnt++;
+ }
+ }
+
+ if (config.params.reversedNested) {
+ aggObj = {};
+ aggObj[newConfigId] = newDsl;
+ newDsl = {
+ reverse_nested: {},
+ aggs: aggObj
+ };
+ newConfigId = subAggPrefix + newConfigId;
+ prevSubAggCnt++;
+ }
+
+ dslLvlCursor[newConfigId] = newDsl;
+
if (config.schema.group === 'buckets' && i < list.length - 1) {
// buckets that are not the last item in the list accept sub-aggs
subAggs = dsl.aggs || (dsl.aggs = {});