Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { getTimerange } from './get_timerange';
import { mapBucket } from './map_bucket';
import { parseSettings } from './parse_settings';

export { overwrite } from './overwrite';

export const helpers = {
bucketTransform,
getAggValue,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
function isObjectInner(val) {
return val != null && typeof val === 'object' && Array.isArray(val) === false;
}

function isObjectObject(o) {
return isObjectInner(o) === true && Object.prototype.toString.call(o) === '[object Object]';
}

export function isPlainObject(o) {
if (isObjectObject(o) === false) return false;

// If has modified constructor
const ctor = o.constructor;
if (typeof ctor !== 'function') return false;

// If has modified prototype
const prot = ctor.prototype;
if (isObjectObject(prot) === false) return false;

// If constructor does not have an Object-specific method
if (prot.hasOwnProperty('isPrototypeOf') === false) {
return false;
}

// Most likely a plain Object
return true;
}

function isObject(val) {
return val !== null && (typeof val === 'object' || typeof val === 'function');
}

function createKey(pattern, options) {
let id = pattern;
if (typeof options === 'undefined') {
return id + '';
}
const keys = Object.keys(options);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
id += ';' + key + '=' + String(options[key]);
}
return id;
}

function isValidKey(key) {
return key !== '__proto__' && key !== 'constructor' && key !== 'prototype';
}

export function set(target, path, value, options) {
if (!isObject(target)) {
return target;
}

const opts = options || {};
const isArray = Array.isArray(path);
if (!isArray && typeof path !== 'string') {
return target;
}

let merge = opts.merge;
if (merge && typeof merge !== 'function') {
merge = Object.assign;
}

const keys = (isArray ? path : split(path, opts)).filter(isValidKey);
const len = keys.length;
const orig = target;

if (!options && keys.length === 1) {
result(target, keys[0], value, merge);
return target;
}

for (let i = 0; i < len; i++) {
const prop = keys[i];

if (!isObject(target[prop])) {
target[prop] = {};
}

if (i === len - 1) {
result(target, prop, value, merge);
break;
}

target = target[prop];
}

return orig;
}

function result(target, path, value, merge) {
if (merge && isPlainObject(target[path]) && isPlainObject(value)) {
target[path] = merge({}, target[path], value);
} else {
target[path] = value;
}
}

function split(path, options) {
const id = createKey(path, options);
if (set.memo[id]) return set.memo[id];

const char = options && options.separator ? options.separator : '.';
let keys = [];
const res = [];

if (options && typeof options.split === 'function') {
keys = options.split(path);
} else {
keys = path.split(char);
}

for (let i = 0; i < keys.length; i++) {
let prop = keys[i];
while (prop && prop.slice(-1) === '\\' && keys[i + 1] != null) {
prop = prop.slice(0, -1) + char + keys[++i];
}
res.push(prop);
}
set.memo[id] = res;
return res;
}

set.memo = {};

/**
* Set path in obj. Behaves like lodash `set`
* @param obj The object to mutate
* @param path The path of the sub-property to set
* @param val The value to set the sub-property to
*/
export function overwrite(obj, path, val) {
set(obj, path, undefined);
set(obj, path, val);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { set, isPlainObject } from './overwrite';

describe('isPlainObject', () => {
test('should return true', () => {
expect(isPlainObject(Object.create({}))).toBe(true);
expect(isPlainObject(Object.create(Object.prototype))).toBe(true);
expect(isPlainObject({ foo: 'bar' })).toBe(true);
expect(isPlainObject({})).toBe(true);
});
test('should return false', () => {
const Foo = jest.fn();
expect(isPlainObject(/foo/)).toBe(false);
expect(isPlainObject(function() {})).toBe(false);
expect(isPlainObject(1)).toBe(false);
expect(isPlainObject(['foo', 'bar'])).toBe(false);
expect(isPlainObject([])).toBe(false);
expect(isPlainObject(Foo)).toBe(false);
expect(isPlainObject(null)).toBe(false);
expect(isPlainObject(Object.create(null))).toBe(false);
});
});
describe('set', () => {
test('should return non-objects', () => {
let res = set('foo', 'a.b', 'c');
expect(res).toEqual('foo');
res = set(null, 'a.b', 'c');
expect(res).toBeNull();
});

test('should create a nested property if it does not already exist', () => {
const o = {};
set(o, 'a.b', 'c');
expect(o.a.b).toEqual('c');
});

test('should merge an existing value with the given value', () => {
const o = { a: { b: { c: 'd' } } };
set(o, 'a.b', { y: 'z' }, { merge: true });
expect(o.a.b).toEqual({ c: 'd', y: 'z' });
});

test('should not split escaped dots', function() {
const o = {};
set(o, 'a\\.b.c.d.e', 'c', { escape: true });
expect(o['a.b'].c.d.e).toEqual('c');
});

test('should work with multiple escaped dots', function() {
const obj1 = {};
set(obj1, 'e\\.f\\.g', 1, { escape: true });
expect(obj1['e.f.g']).toEqual(1);

const obj2 = {};
set(obj2, 'e\\.f.g\\.h\\.i.j', 1, { escape: true });
expect(obj2).toEqual({ 'e.f': { 'g.h.i': { j: 1 } } });
});

test('should work with escaped dots as the last character', function() {
const o = {};
set(o, 'a\\.b.c.d\\.e\\.', 'c', { escape: true });
expect(o['a.b'].c['d.e.']).toEqual('c');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
* specific language governing permissions and limitations
* under the License.
*/

import _ from 'lodash';
import { dateHistogramInterval } from '../../../../../../data/server';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { getTimerange } from '../../helpers/get_timerange';
import { overwrite } from '../../helpers';

export function dateHistogram(
req,
Expand All @@ -36,7 +35,7 @@ export function dateHistogram(
const { from, to } = getTimerange(req);
const timezone = capabilities.searchTimezone;

_.set(doc, `aggs.${annotation.id}.date_histogram`, {
overwrite(doc, `aggs.${annotation.id}.date_histogram`, {
field: timeField,
min_doc_count: 0,
time_zone: timezone,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
* under the License.
*/

import _ from 'lodash';
import { overwrite } from '../../helpers';

export function topHits(req, panel, annotation) {
return next => doc => {
const fields = (annotation.fields && annotation.fields.split(/[,\s]+/)) || [];
const timeField = annotation.time_field;
_.set(doc, `aggs.${annotation.id}.aggs.hits.top_hits`, {
overwrite(doc, `aggs.${annotation.id}.aggs.hits.top_hits`, {
sort: [
{
[timeField]: { order: 'desc' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { set } from 'lodash';
import { overwrite } from '../../helpers';
import { dateHistogramInterval } from '../../../../../../data/server';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { offsetTime } from '../../offset_time';
Expand All @@ -33,7 +33,7 @@ export function dateHistogram(req, panel, series, esQueryConfig, indexPatternObj
const { from, to } = offsetTime(req, series.offset_time);
const timezone = capabilities.searchTimezone;

set(doc, `aggs.${series.id}.aggs.timeseries.date_histogram`, {
overwrite(doc, `aggs.${series.id}.aggs.timeseries.date_histogram`, {
field: timeField,
min_doc_count: 0,
time_zone: timezone,
Expand All @@ -46,7 +46,7 @@ export function dateHistogram(req, panel, series, esQueryConfig, indexPatternObj
};

const getDateHistogramForEntireTimerangeMode = () =>
set(doc, `aggs.${series.id}.aggs.timeseries.auto_date_histogram`, {
overwrite(doc, `aggs.${series.id}.aggs.timeseries.auto_date_histogram`, {
field: timeField,
buckets: 1,
});
Expand All @@ -57,7 +57,7 @@ export function dateHistogram(req, panel, series, esQueryConfig, indexPatternObj

// master

set(doc, `aggs.${series.id}.meta`, {
overwrite(doc, `aggs.${series.id}.meta`, {
timeField,
intervalString,
bucketSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@

const filter = metric => metric.type === 'filter_ratio';
import { bucketTransform } from '../../helpers/bucket_transform';
import _ from 'lodash';
import { overwrite } from '../../helpers';

export function ratios(req, panel, series) {
return next => doc => {
if (series.metrics.some(filter)) {
series.metrics.filter(filter).forEach(metric => {
_.set(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-numerator.filter`, {
overwrite(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-numerator.filter`, {
query_string: { query: metric.numerator || '*', analyze_wildcard: true },
});
_.set(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-denominator.filter`, {
overwrite(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-denominator.filter`, {
query_string: { query: metric.denominator || '*', analyze_wildcard: true },
});

Expand All @@ -46,8 +46,12 @@ export function ratios(req, panel, series) {
metricAgg = {};
}
const aggBody = { metric: metricAgg };
_.set(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-numerator.aggs`, aggBody);
_.set(
overwrite(
doc,
`aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-numerator.aggs`,
aggBody
);
overwrite(
doc,
`aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-denominator.aggs`,
aggBody
Expand All @@ -56,7 +60,7 @@ export function ratios(req, panel, series) {
denominatorPath = `${metric.id}-denominator>metric`;
}

_.set(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}`, {
overwrite(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}`, {
bucket_script: {
buckets_path: {
numerator: numeratorPath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import _ from 'lodash';
import { overwrite } from '../../helpers';
import { getBucketSize } from '../../helpers/get_bucket_size';
import { bucketTransform } from '../../helpers/bucket_transform';
import { getIntervalAndTimefield } from '../../get_interval_and_timefield';
Expand All @@ -33,7 +33,7 @@ export function metricBuckets(req, panel, series, esQueryConfig, indexPatternObj
if (fn) {
try {
const bucket = fn(metric, series.metrics, intervalString);
_.set(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}`, bucket);
overwrite(doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}`, bucket);
} catch (e) {
// meh
}
Expand Down
Loading