[ui/public/utils] Copy rarely used items to where they are consumed (#53819)
* [ui/public/utils] Copy rarely used items to where they are consumed Closes: #52841 * sort_prefix_first 👉x-pack/legacy/plugins/kuery_autocomplete * numeric 👉src/legacy/core_plugins/kibana/public/management * diff_object + tests 👉ui/state_management * function + tests 👉ui/state_management (function.js was removed!) * key_map 👉ui/directives * leastCommonMultiple 👉ui/vis * string_utils 👉ui/saved_objects * collection * parse_interval * it -> test * fix CI * fix PR comments Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
007ea5fc46
commit
71ff2de7e1
|
@ -29,7 +29,7 @@ import { uiModules } from 'ui/modules';
|
|||
import { fatalError, toastNotifications } from 'ui/notify';
|
||||
import 'ui/accessibility/kbn_ui_ace_keyboard_mode';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { isNumeric } from 'ui/utils/numeric';
|
||||
import { isNumeric } from './lib/numeric';
|
||||
import { canViewInApp } from './lib/in_app_url';
|
||||
|
||||
import { castEsToKbnFieldTypeName } from '../../../../../../../plugins/data/public';
|
||||
|
|
|
@ -17,17 +17,20 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { partition } from 'lodash';
|
||||
import { keysToCamelCaseShallow } from './case_conversion';
|
||||
|
||||
export function sortPrefixFirst(array: any[], prefix?: string | number, property?: string): any[] {
|
||||
if (!prefix) {
|
||||
return array;
|
||||
}
|
||||
const lowerCasePrefix = ('' + prefix).toLowerCase();
|
||||
describe('keysToCamelCaseShallow', () => {
|
||||
test("should convert all of an object's keys to camel case", () => {
|
||||
const data = {
|
||||
camelCase: 'camelCase',
|
||||
'kebab-case': 'kebabCase',
|
||||
snake_case: 'snakeCase',
|
||||
};
|
||||
|
||||
const partitions = partition(array, entry => {
|
||||
const value = ('' + (property ? entry[property] : entry)).toLowerCase();
|
||||
return value.startsWith(lowerCasePrefix);
|
||||
const result = keysToCamelCaseShallow(data);
|
||||
|
||||
expect(result.camelCase).toBe('camelCase');
|
||||
expect(result.kebabCase).toBe('kebabCase');
|
||||
expect(result.snakeCase).toBe('snakeCase');
|
||||
});
|
||||
return [...partitions[0], ...partitions[1]];
|
||||
}
|
||||
});
|
|
@ -17,6 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { mapKeys, camelCase } from 'lodash';
|
||||
|
||||
export function parseInterval(interval: string): moment.Duration | null;
|
||||
export function keysToCamelCaseShallow(object: Record<string, any>) {
|
||||
return mapKeys(object, (value, key) => camelCase(key));
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
import { keysToCamelCaseShallow } from 'ui/utils/case_conversion';
|
||||
import { keysToCamelCaseShallow } from './case_conversion';
|
||||
|
||||
export async function findObjects(findOptions) {
|
||||
const response = await kfetch({
|
||||
|
@ -26,5 +26,6 @@ export async function findObjects(findOptions) {
|
|||
pathname: '/api/kibana/management/saved_objects/_find',
|
||||
query: findOptions,
|
||||
});
|
||||
|
||||
return keysToCamelCaseShallow(response);
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { isNaN } from 'lodash';
|
||||
|
||||
export function isNumeric(v: any): boolean {
|
||||
return !_.isNaN(v) && (typeof v === 'number' || (!Array.isArray(v) && !_.isNaN(parseFloat(v))));
|
||||
return !isNaN(v) && (typeof v === 'number' || (!Array.isArray(v) && !isNaN(parseFloat(v))));
|
||||
}
|
|
@ -17,9 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { StringUtils } from 'ui/utils/string_utils';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const upperFirst = (str = '') => str.replace(/^./, str => str.toUpperCase());
|
||||
|
||||
const names = {
|
||||
general: i18n.translate('kbn.management.settings.categoryNames.generalLabel', {
|
||||
defaultMessage: 'General',
|
||||
|
@ -51,5 +52,5 @@ const names = {
|
|||
};
|
||||
|
||||
export function getCategoryName(category) {
|
||||
return category ? names[category] || StringUtils.upperFirst(category) : '';
|
||||
return category ? names[category] || upperFirst(category) : '';
|
||||
}
|
||||
|
|
|
@ -22,22 +22,21 @@ import _ from 'lodash';
|
|||
/**
|
||||
* just a place to put feature detection checks
|
||||
*/
|
||||
export const supports = {
|
||||
cssFilters: (function() {
|
||||
const e = document.createElement('img');
|
||||
const rules = ['webkitFilter', 'mozFilter', 'msFilter', 'filter'];
|
||||
const test = 'grayscale(1)';
|
||||
rules.forEach(function(rule) {
|
||||
e.style[rule] = test;
|
||||
});
|
||||
export const supportsCssFilters = (function() {
|
||||
const e = document.createElement('img');
|
||||
const rules = ['webkitFilter', 'mozFilter', 'msFilter', 'filter'];
|
||||
const test = 'grayscale(1)';
|
||||
|
||||
document.body.appendChild(e);
|
||||
const styles = window.getComputedStyle(e);
|
||||
const can = _(styles)
|
||||
.pick(rules)
|
||||
.includes(test);
|
||||
document.body.removeChild(e);
|
||||
rules.forEach(function(rule) {
|
||||
e.style[rule] = test;
|
||||
});
|
||||
|
||||
return can;
|
||||
})(),
|
||||
};
|
||||
document.body.appendChild(e);
|
||||
const styles = window.getComputedStyle(e);
|
||||
const can = _(styles)
|
||||
.pick(rules)
|
||||
.includes(test);
|
||||
document.body.removeChild(e);
|
||||
|
||||
return can;
|
||||
})();
|
|
@ -20,7 +20,6 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { supports } from 'ui/utils/supports';
|
||||
import { Schemas } from 'ui/vis/editors/default/schemas';
|
||||
import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps';
|
||||
import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson';
|
||||
|
@ -29,6 +28,7 @@ import { createTileMapVisualization } from './tile_map_visualization';
|
|||
import { Status } from '../../visualizations/public';
|
||||
import { TileMapOptions } from './components/tile_map_options';
|
||||
import { MapTypes } from './map_types';
|
||||
import { supportsCssFilters } from './css_filters';
|
||||
|
||||
export function createTileMapTypeDefinition(dependencies) {
|
||||
const CoordinateMapsVisualization = createTileMapVisualization(dependencies);
|
||||
|
@ -44,7 +44,7 @@ export function createTileMapTypeDefinition(dependencies) {
|
|||
defaultMessage: 'Plot latitude and longitude coordinates on a map',
|
||||
}),
|
||||
visConfig: {
|
||||
canDesaturate: !!supports.cssFilters,
|
||||
canDesaturate: Boolean(supportsCssFilters),
|
||||
defaults: {
|
||||
colorSchema: 'Yellow to Red',
|
||||
mapType: 'Scaled Circle Markers',
|
||||
|
|
|
@ -19,12 +19,12 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import rison from 'rison-node';
|
||||
import { keyMap } from 'ui/utils/key_map';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import 'ui/directives/input_focus';
|
||||
import 'ui/directives/paginate';
|
||||
import savedObjectFinderTemplate from './saved_object_finder.html';
|
||||
import { savedSheetLoader } from '../services/saved_sheets';
|
||||
import { keyMap } from 'ui/directives/key_map';
|
||||
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { parseInterval } from 'ui/utils/parse_interval';
|
||||
import { GTE_INTERVAL_RE } from '../../common/interval_regexp';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { parseInterval } from '../../../../../plugins/data/public';
|
||||
|
||||
export function validateInterval(bounds, panel, maxBuckets) {
|
||||
const { interval } = panel;
|
||||
|
|
36
src/legacy/server/status/lib/case_conversion.test.ts
Normal file
36
src/legacy/server/status/lib/case_conversion.test.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { keysToSnakeCaseShallow } from './case_conversion';
|
||||
|
||||
describe('keysToSnakeCaseShallow', () => {
|
||||
test("should convert all of an object's keys to snake case", () => {
|
||||
const data = {
|
||||
camelCase: 'camel_case',
|
||||
'kebab-case': 'kebab_case',
|
||||
snake_case: 'snake_case',
|
||||
};
|
||||
|
||||
const result = keysToSnakeCaseShallow(data);
|
||||
|
||||
expect(result.camel_case).toBe('camel_case');
|
||||
expect(result.kebab_case).toBe('kebab_case');
|
||||
expect(result.snake_case).toBe('snake_case');
|
||||
});
|
||||
});
|
|
@ -17,16 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { mapKeys, snakeCase } from 'lodash';
|
||||
|
||||
export function keysToSnakeCaseShallow(object: Record<string, any>) {
|
||||
return _.mapKeys(object, (value, key) => {
|
||||
return _.snakeCase(key);
|
||||
});
|
||||
}
|
||||
|
||||
export function keysToCamelCaseShallow(object: Record<string, any>) {
|
||||
return _.mapKeys(object, (value, key) => {
|
||||
return _.camelCase(key);
|
||||
});
|
||||
return mapKeys(object, (value, key) => snakeCase(key));
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
import os from 'os';
|
||||
import v8 from 'v8';
|
||||
import { get, isObject, merge } from 'lodash';
|
||||
import { keysToSnakeCaseShallow } from '../../../utils/case_conversion';
|
||||
import { keysToSnakeCaseShallow } from './case_conversion';
|
||||
import { getAllStats as cGroupStats } from './cgroup';
|
||||
import { getOSInfo } from './get_os_info';
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import { uiModules } from '../../modules';
|
||||
import { callEach } from '../../utils/function';
|
||||
|
||||
export function watchMultiDecorator($provide) {
|
||||
$provide.decorator('$rootScope', function($delegate) {
|
||||
|
@ -112,7 +111,9 @@ export function watchMultiDecorator($provide) {
|
|||
)
|
||||
);
|
||||
|
||||
return _.partial(callEach, unwatchers);
|
||||
return function() {
|
||||
unwatchers.forEach(listener => listener());
|
||||
};
|
||||
};
|
||||
|
||||
function normalizeExpression($scope, expr) {
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { groupBy } from 'lodash';
|
||||
import { organizeBy } from './organize_by';
|
||||
|
||||
describe('organizeBy', () => {
|
||||
test('it works', () => {
|
||||
const col = [
|
||||
{
|
||||
name: 'one',
|
||||
roles: ['user', 'admin', 'owner'],
|
||||
},
|
||||
{
|
||||
name: 'two',
|
||||
roles: ['user'],
|
||||
},
|
||||
{
|
||||
name: 'three',
|
||||
roles: ['user'],
|
||||
},
|
||||
{
|
||||
name: 'four',
|
||||
roles: ['user', 'admin'],
|
||||
},
|
||||
];
|
||||
|
||||
const resp = organizeBy(col, 'roles');
|
||||
expect(resp).toHaveProperty('user');
|
||||
expect(resp.user.length).toBe(4);
|
||||
|
||||
expect(resp).toHaveProperty('admin');
|
||||
expect(resp.admin.length).toBe(2);
|
||||
|
||||
expect(resp).toHaveProperty('owner');
|
||||
expect(resp.owner.length).toBe(1);
|
||||
});
|
||||
|
||||
test('behaves just like groupBy in normal scenarios', () => {
|
||||
const col = [{ name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four' }];
|
||||
|
||||
const orgs = organizeBy(col, 'name');
|
||||
const groups = groupBy(col, 'name');
|
||||
|
||||
expect(orgs).toEqual(groups);
|
||||
});
|
||||
});
|
61
src/legacy/ui/public/indexed_array/helpers/organize_by.ts
Normal file
61
src/legacy/ui/public/indexed_array/helpers/organize_by.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 { each, isFunction } from 'lodash';
|
||||
|
||||
/**
|
||||
* Like _.groupBy, but allows specifying multiple groups for a
|
||||
* single object.
|
||||
*
|
||||
* organizeBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a')
|
||||
* // Object {1: Array[2], 2: Array[1], 3: Array[1], 4: Array[1]}
|
||||
*
|
||||
* _.groupBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a')
|
||||
* // Object {'1,2,3': Array[1], '1,4': Array[1]}
|
||||
*
|
||||
* @param {array} collection - the list of values to organize
|
||||
* @param {Function} callback - either a property name, or a callback.
|
||||
* @return {object}
|
||||
*/
|
||||
export function organizeBy(collection: object[], callback: ((obj: object) => string) | string) {
|
||||
const buckets: { [key: string]: object[] } = {};
|
||||
|
||||
function add(key: string, obj: object) {
|
||||
if (!buckets[key]) {
|
||||
buckets[key] = [];
|
||||
}
|
||||
buckets[key].push(obj);
|
||||
}
|
||||
|
||||
each(collection, (obj: Record<string, any>) => {
|
||||
const keys = isFunction(callback) ? callback(obj) : obj[callback];
|
||||
|
||||
if (!Array.isArray(keys)) {
|
||||
add(keys, obj);
|
||||
return;
|
||||
}
|
||||
|
||||
let length = keys.length;
|
||||
while (length-- > 0) {
|
||||
add(keys[length], obj);
|
||||
}
|
||||
});
|
||||
|
||||
return buckets;
|
||||
}
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import { inflector } from './inflector';
|
||||
import { organizeBy } from '../utils/collection';
|
||||
import { organizeBy } from './helpers/organize_by';
|
||||
|
||||
const pathGetter = _(_.get)
|
||||
.rearg(1, 0)
|
||||
|
|
|
@ -16,17 +16,17 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { StringUtils } from './string_utils';
|
||||
|
||||
import _ from 'lodash';
|
||||
describe('StringUtils class', () => {
|
||||
describe('static upperFirst', () => {
|
||||
test('should converts the first character of string to upper case', () => {
|
||||
expect(StringUtils.upperFirst()).toBe('');
|
||||
expect(StringUtils.upperFirst('')).toBe('');
|
||||
|
||||
/**
|
||||
* Call all of the function in an array
|
||||
*
|
||||
* @param {array[functions]} arr
|
||||
* @return {undefined}
|
||||
*/
|
||||
export function callEach(arr) {
|
||||
return _.map(arr, function(fn) {
|
||||
return _.isFunction(fn) ? fn() : undefined;
|
||||
expect(StringUtils.upperFirst('Fred')).toBe('Fred');
|
||||
expect(StringUtils.upperFirst('fred')).toBe('Fred');
|
||||
expect(StringUtils.upperFirst('FRED')).toBe('FRED');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
|
@ -23,7 +23,7 @@ export class StringUtils {
|
|||
* @param str {string}
|
||||
* @returns {string}
|
||||
*/
|
||||
public static upperFirst(str: string): string {
|
||||
public static upperFirst(str: string = ''): string {
|
||||
return str ? str.charAt(0).toUpperCase() + str.slice(1) : '';
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
import { SavedObject } from 'ui/saved_objects/types';
|
||||
import { ChromeStart, SavedObjectsClientContract, SavedObjectsFindOptions } from 'kibana/public';
|
||||
import { StringUtils } from '../utils/string_utils';
|
||||
import { StringUtils } from './helpers/string_utils';
|
||||
|
||||
/**
|
||||
* The SavedObjectLoader class provides some convenience functions
|
||||
|
|
|
@ -31,7 +31,6 @@ import { uiModules } from '../modules';
|
|||
import { StateProvider } from './state';
|
||||
import '../persisted_state';
|
||||
import { createLegacyClass } from '../utils/legacy_class';
|
||||
import { callEach } from '../utils/function';
|
||||
|
||||
const urlParam = '_a';
|
||||
|
||||
|
@ -62,7 +61,8 @@ export function AppStateProvider(Private, $location, $injector) {
|
|||
AppState.prototype.destroy = function() {
|
||||
AppState.Super.prototype.destroy.call(this);
|
||||
AppState.getAppState._set(null);
|
||||
callEach(eventUnsubscribers);
|
||||
|
||||
eventUnsubscribers.forEach(listener => listener());
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,12 +29,11 @@ import _ from 'lodash';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import angular from 'angular';
|
||||
import rison from 'rison-node';
|
||||
import { applyDiff } from '../utils/diff_object';
|
||||
import { applyDiff } from './utils/diff_object';
|
||||
import { EventsProvider } from '../events';
|
||||
import { fatalError, toastNotifications } from '../notify';
|
||||
import './config_provider';
|
||||
import { createLegacyClass } from '../utils/legacy_class';
|
||||
import { callEach } from '../utils/function';
|
||||
import {
|
||||
hashedItemStore,
|
||||
isStateHash,
|
||||
|
@ -66,7 +65,7 @@ export function StateProvider(
|
|||
this._hashedItemStore = _hashedItemStore;
|
||||
|
||||
// When the URL updates we need to fetch the values from the URL
|
||||
this._cleanUpListeners = _.partial(callEach, [
|
||||
this._cleanUpListeners = [
|
||||
// partial route update, no app reload
|
||||
$rootScope.$on('$routeUpdate', () => {
|
||||
this.fetch();
|
||||
|
@ -85,7 +84,7 @@ export function StateProvider(
|
|||
this.fetch();
|
||||
}
|
||||
}),
|
||||
]);
|
||||
];
|
||||
|
||||
// Initialize the State with fetch
|
||||
this.fetch();
|
||||
|
@ -242,7 +241,9 @@ export function StateProvider(
|
|||
*/
|
||||
State.prototype.destroy = function() {
|
||||
this.off(); // removes all listeners
|
||||
this._cleanUpListeners(); // Removes the $routeUpdate listener
|
||||
|
||||
// Removes the $routeUpdate listener
|
||||
this._cleanUpListeners.forEach(listener => listener(this));
|
||||
};
|
||||
|
||||
State.prototype.setDefaults = function(defaults) {
|
||||
|
|
|
@ -17,76 +17,88 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import _ from 'lodash';
|
||||
import { applyDiff } from '../diff_object';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { applyDiff } from './diff_object';
|
||||
|
||||
describe('ui/utils/diff_object', function() {
|
||||
it('should list the removed keys', function() {
|
||||
describe('diff_object', () => {
|
||||
test('should list the removed keys', () => {
|
||||
const target = { test: 'foo' };
|
||||
const source = { foo: 'test' };
|
||||
const results = applyDiff(target, source);
|
||||
expect(results).to.have.property('removed');
|
||||
expect(results.removed).to.eql(['test']);
|
||||
|
||||
expect(results).toHaveProperty('removed');
|
||||
expect(results.removed).toEqual(['test']);
|
||||
});
|
||||
|
||||
it('should list the changed keys', function() {
|
||||
test('should list the changed keys', () => {
|
||||
const target = { foo: 'bar' };
|
||||
const source = { foo: 'test' };
|
||||
const results = applyDiff(target, source);
|
||||
expect(results).to.have.property('changed');
|
||||
expect(results.changed).to.eql(['foo']);
|
||||
|
||||
expect(results).toHaveProperty('changed');
|
||||
expect(results.changed).toEqual(['foo']);
|
||||
});
|
||||
|
||||
it('should list the added keys', function() {
|
||||
test('should list the added keys', () => {
|
||||
const target = {};
|
||||
const source = { foo: 'test' };
|
||||
const results = applyDiff(target, source);
|
||||
expect(results).to.have.property('added');
|
||||
expect(results.added).to.eql(['foo']);
|
||||
|
||||
expect(results).toHaveProperty('added');
|
||||
expect(results.added).toEqual(['foo']);
|
||||
});
|
||||
|
||||
it('should list all the keys that are change or removed', function() {
|
||||
test('should list all the keys that are change or removed', () => {
|
||||
const target = { foo: 'bar', test: 'foo' };
|
||||
const source = { foo: 'test' };
|
||||
const results = applyDiff(target, source);
|
||||
expect(results).to.have.property('keys');
|
||||
expect(results.keys).to.eql(['foo', 'test']);
|
||||
|
||||
expect(results).toHaveProperty('keys');
|
||||
expect(results.keys).toEqual(['foo', 'test']);
|
||||
});
|
||||
|
||||
it('should ignore functions', function() {
|
||||
test('should ignore functions', () => {
|
||||
const target = { foo: 'bar', test: 'foo' };
|
||||
const source = { foo: 'test', fn: _.noop };
|
||||
const source = { foo: 'test', fn: () => {} };
|
||||
|
||||
applyDiff(target, source);
|
||||
expect(target).to.not.have.property('fn');
|
||||
|
||||
expect(target).not.toHaveProperty('fn');
|
||||
});
|
||||
|
||||
it('should ignore underscores', function() {
|
||||
test('should ignore underscores', () => {
|
||||
const target = { foo: 'bar', test: 'foo' };
|
||||
const source = { foo: 'test', _private: 'foo' };
|
||||
|
||||
applyDiff(target, source);
|
||||
expect(target).to.not.have.property('_private');
|
||||
|
||||
expect(target).not.toHaveProperty('_private');
|
||||
});
|
||||
|
||||
it('should ignore dollar signs', function() {
|
||||
test('should ignore dollar signs', () => {
|
||||
const target = { foo: 'bar', test: 'foo' };
|
||||
const source = { foo: 'test', $private: 'foo' };
|
||||
|
||||
applyDiff(target, source);
|
||||
expect(target).to.not.have.property('$private');
|
||||
|
||||
expect(target).not.toHaveProperty('$private');
|
||||
});
|
||||
|
||||
it('should not list any changes for similar objects', function() {
|
||||
test('should not list any changes for similar objects', () => {
|
||||
const target = { foo: 'bar', test: 'foo' };
|
||||
const source = { foo: 'bar', test: 'foo', $private: 'foo' };
|
||||
const results = applyDiff(target, source);
|
||||
expect(results.changed).to.be.empty();
|
||||
|
||||
expect(results.changed).toEqual([]);
|
||||
});
|
||||
|
||||
it('should only change keys that actually changed', function() {
|
||||
test('should only change keys that actually changed', () => {
|
||||
const obj = { message: 'foo' };
|
||||
const target = { obj: obj, message: 'foo' };
|
||||
const source = { obj: _.cloneDeep(obj), message: 'test' };
|
||||
const target = { obj, message: 'foo' };
|
||||
const source = { obj: cloneDeep(obj), message: 'test' };
|
||||
|
||||
applyDiff(target, source);
|
||||
expect(target.obj).to.be(obj);
|
||||
|
||||
expect(target.obj).toBe(obj);
|
||||
});
|
||||
});
|
75
src/legacy/ui/public/state_management/utils/diff_object.ts
Normal file
75
src/legacy/ui/public/state_management/utils/diff_object.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { keys, isFunction, difference, filter, union, pick, each, assign, isEqual } from 'lodash';
|
||||
|
||||
export interface IDiffObject {
|
||||
removed: string[];
|
||||
added: string[];
|
||||
changed: string[];
|
||||
keys: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the private vars
|
||||
* @param {string} key The keys
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const filterPrivateAndMethods = function(obj: Record<string, any>) {
|
||||
return function(key: string) {
|
||||
if (isFunction(obj[key])) return false;
|
||||
if (key.charAt(0) === '$') return false;
|
||||
return key.charAt(0) !== '_';
|
||||
};
|
||||
};
|
||||
|
||||
export function applyDiff(target: Record<string, any>, source: Record<string, any>) {
|
||||
const diff: IDiffObject = {
|
||||
removed: [],
|
||||
added: [],
|
||||
changed: [],
|
||||
keys: [],
|
||||
};
|
||||
|
||||
const targetKeys = keys(target).filter(filterPrivateAndMethods(target));
|
||||
const sourceKeys = keys(source).filter(filterPrivateAndMethods(source));
|
||||
|
||||
// Find the keys to be removed
|
||||
diff.removed = difference(targetKeys, sourceKeys);
|
||||
|
||||
// Find the keys to be added
|
||||
diff.added = difference(sourceKeys, targetKeys);
|
||||
|
||||
// Find the keys that will be changed
|
||||
diff.changed = filter(sourceKeys, key => !isEqual(target[key], source[key]));
|
||||
|
||||
// Make a list of all the keys that are changing
|
||||
diff.keys = union(diff.changed, diff.removed, diff.added);
|
||||
|
||||
// Remove all the keys
|
||||
each(diff.removed, key => {
|
||||
delete target[key];
|
||||
});
|
||||
|
||||
// Assign the changed to the source to the target
|
||||
assign(target, pick(source, diff.changed));
|
||||
// Assign the added to the source to the target
|
||||
assign(target, pick(source, diff.added));
|
||||
|
||||
return diff;
|
||||
}
|
|
@ -20,13 +20,12 @@
|
|||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { parseInterval } from '../utils/parse_interval';
|
||||
import { calcAutoIntervalLessThan, calcAutoIntervalNear } from './calc_auto_interval';
|
||||
import {
|
||||
convertDurationToNormalizedEsInterval,
|
||||
convertIntervalToEsInterval,
|
||||
} from './calc_es_interval';
|
||||
import { FIELD_FORMAT_IDS } from '../../../../plugins/data/public';
|
||||
import { FIELD_FORMAT_IDS, parseInterval } from '../../../../plugins/data/public';
|
||||
|
||||
const getConfig = (...args) => npStart.core.uiSettings.get(...args);
|
||||
|
||||
|
|
|
@ -1,200 +0,0 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { groupBy } from 'lodash';
|
||||
import { move, pushAll, organizeBy } from '../collection';
|
||||
|
||||
describe('collection', () => {
|
||||
describe('move', function() {
|
||||
it('accepts previous from->to syntax', function() {
|
||||
const list = [1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1];
|
||||
|
||||
expect(list[3]).to.be(1);
|
||||
expect(list[8]).to.be(8);
|
||||
|
||||
move(list, 8, 3);
|
||||
|
||||
expect(list[8]).to.be(1);
|
||||
expect(list[3]).to.be(8);
|
||||
});
|
||||
|
||||
it('moves an object up based on a function callback', function() {
|
||||
const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1];
|
||||
|
||||
expect(list[4]).to.be(0);
|
||||
expect(list[5]).to.be(1);
|
||||
expect(list[6]).to.be(0);
|
||||
|
||||
move(list, 5, false, function(v) {
|
||||
return v === 0;
|
||||
});
|
||||
|
||||
expect(list[4]).to.be(1);
|
||||
expect(list[5]).to.be(0);
|
||||
expect(list[6]).to.be(0);
|
||||
});
|
||||
|
||||
it('moves an object down based on a function callback', function() {
|
||||
const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1];
|
||||
|
||||
expect(list[4]).to.be(0);
|
||||
expect(list[5]).to.be(1);
|
||||
expect(list[6]).to.be(0);
|
||||
|
||||
move(list, 5, true, function(v) {
|
||||
return v === 0;
|
||||
});
|
||||
|
||||
expect(list[4]).to.be(0);
|
||||
expect(list[5]).to.be(0);
|
||||
expect(list[6]).to.be(1);
|
||||
});
|
||||
|
||||
it('moves an object up based on a where callback', function() {
|
||||
const list = [
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 0 },
|
||||
{ v: 1 },
|
||||
{ v: 0 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
];
|
||||
|
||||
expect(list[4]).to.have.property('v', 0);
|
||||
expect(list[5]).to.have.property('v', 1);
|
||||
expect(list[6]).to.have.property('v', 0);
|
||||
|
||||
move(list, 5, false, { v: 0 });
|
||||
|
||||
expect(list[4]).to.have.property('v', 1);
|
||||
expect(list[5]).to.have.property('v', 0);
|
||||
expect(list[6]).to.have.property('v', 0);
|
||||
});
|
||||
|
||||
it('moves an object down based on a where callback', function() {
|
||||
const list = [
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 0 },
|
||||
{ v: 1 },
|
||||
{ v: 0 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
];
|
||||
|
||||
expect(list[4]).to.have.property('v', 0);
|
||||
expect(list[5]).to.have.property('v', 1);
|
||||
expect(list[6]).to.have.property('v', 0);
|
||||
|
||||
move(list, 5, true, { v: 0 });
|
||||
|
||||
expect(list[4]).to.have.property('v', 0);
|
||||
expect(list[5]).to.have.property('v', 0);
|
||||
expect(list[6]).to.have.property('v', 1);
|
||||
});
|
||||
|
||||
it('moves an object down based on a pluck callback', function() {
|
||||
const list = [
|
||||
{ id: 0, normal: true },
|
||||
{ id: 1, normal: true },
|
||||
{ id: 2, normal: true },
|
||||
{ id: 3, normal: true },
|
||||
{ id: 4, normal: true },
|
||||
{ id: 5, normal: false },
|
||||
{ id: 6, normal: true },
|
||||
{ id: 7, normal: true },
|
||||
{ id: 8, normal: true },
|
||||
{ id: 9, normal: true },
|
||||
];
|
||||
|
||||
expect(list[4]).to.have.property('id', 4);
|
||||
expect(list[5]).to.have.property('id', 5);
|
||||
expect(list[6]).to.have.property('id', 6);
|
||||
|
||||
move(list, 5, true, 'normal');
|
||||
|
||||
expect(list[4]).to.have.property('id', 4);
|
||||
expect(list[5]).to.have.property('id', 6);
|
||||
expect(list[6]).to.have.property('id', 5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pushAll', function() {
|
||||
it('pushes an entire array into another', function() {
|
||||
const a = [1, 2, 3, 4];
|
||||
const b = [5, 6, 7, 8];
|
||||
|
||||
const output = pushAll(b, a);
|
||||
expect(output).to.be(a);
|
||||
expect(a).to.eql([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||
expect(b).to.eql([5, 6, 7, 8]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('organizeBy', function() {
|
||||
it('it works', function() {
|
||||
const col = [
|
||||
{
|
||||
name: 'one',
|
||||
roles: ['user', 'admin', 'owner'],
|
||||
},
|
||||
{
|
||||
name: 'two',
|
||||
roles: ['user'],
|
||||
},
|
||||
{
|
||||
name: 'three',
|
||||
roles: ['user'],
|
||||
},
|
||||
{
|
||||
name: 'four',
|
||||
roles: ['user', 'admin'],
|
||||
},
|
||||
];
|
||||
|
||||
const resp = organizeBy(col, 'roles');
|
||||
expect(resp).to.have.property('user');
|
||||
expect(resp.user).to.have.length(4);
|
||||
|
||||
expect(resp).to.have.property('admin');
|
||||
expect(resp.admin).to.have.length(2);
|
||||
|
||||
expect(resp).to.have.property('owner');
|
||||
expect(resp.owner).to.have.length(1);
|
||||
});
|
||||
|
||||
it('behaves just like groupBy in normal scenarios', function() {
|
||||
const col = [{ name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four' }];
|
||||
|
||||
const orgs = organizeBy(col, 'name');
|
||||
const groups = groupBy(col, 'name');
|
||||
expect(orgs).to.eql(groups);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* 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 { parseInterval } from '../parse_interval';
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
describe('parseInterval', function() {
|
||||
it('should correctly parse an interval containing unit and value', function() {
|
||||
let duration = parseInterval('1d');
|
||||
expect(duration.as('d')).to.be(1);
|
||||
|
||||
duration = parseInterval('2y');
|
||||
expect(duration.as('y')).to.be(2);
|
||||
|
||||
duration = parseInterval('5M');
|
||||
expect(duration.as('M')).to.be(5);
|
||||
|
||||
duration = parseInterval('5m');
|
||||
expect(duration.as('m')).to.be(5);
|
||||
|
||||
duration = parseInterval('250ms');
|
||||
expect(duration.as('ms')).to.be(250);
|
||||
|
||||
duration = parseInterval('100s');
|
||||
expect(duration.as('s')).to.be(100);
|
||||
|
||||
duration = parseInterval('23d');
|
||||
expect(duration.as('d')).to.be(23);
|
||||
|
||||
duration = parseInterval('52w');
|
||||
expect(duration.as('w')).to.be(52);
|
||||
});
|
||||
|
||||
it('should correctly parse fractional intervals containing unit and value', function() {
|
||||
let duration = parseInterval('1.5w');
|
||||
expect(duration.as('w')).to.be(1.5);
|
||||
|
||||
duration = parseInterval('2.35y');
|
||||
expect(duration.as('y')).to.be(2.35);
|
||||
});
|
||||
|
||||
it('should correctly bubble up intervals which are less than 1', function() {
|
||||
let duration = parseInterval('0.5y');
|
||||
expect(duration.as('d')).to.be(183);
|
||||
|
||||
duration = parseInterval('0.5d');
|
||||
expect(duration.as('h')).to.be(12);
|
||||
});
|
||||
|
||||
it('should correctly parse a unit in an interval only', function() {
|
||||
let duration = parseInterval('ms');
|
||||
expect(duration.as('ms')).to.be(1);
|
||||
|
||||
duration = parseInterval('d');
|
||||
expect(duration.as('d')).to.be(1);
|
||||
|
||||
duration = parseInterval('m');
|
||||
expect(duration.as('m')).to.be(1);
|
||||
|
||||
duration = parseInterval('y');
|
||||
expect(duration.as('y')).to.be(1);
|
||||
|
||||
duration = parseInterval('M');
|
||||
expect(duration.as('M')).to.be(1);
|
||||
});
|
||||
|
||||
it('should return null for an invalid interval', function() {
|
||||
let duration = parseInterval('');
|
||||
expect(duration).to.not.be.ok();
|
||||
|
||||
duration = parseInterval(null);
|
||||
expect(duration).to.not.be.ok();
|
||||
|
||||
duration = parseInterval('234asdf');
|
||||
expect(duration).to.not.be.ok();
|
||||
});
|
||||
});
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
import { sortPrefixFirst } from '../sort_prefix_first';
|
||||
|
||||
describe('sortPrefixFirst', function() {
|
||||
it('should return the original unmodified array if no prefix is provided', function() {
|
||||
const array = ['foo', 'bar', 'baz'];
|
||||
const result = sortPrefixFirst(array);
|
||||
expect(result).to.be(array);
|
||||
expect(result).to.eql(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
it('should sort items that match the prefix first without modifying the original array', function() {
|
||||
const array = ['foo', 'bar', 'baz'];
|
||||
const result = sortPrefixFirst(array, 'b');
|
||||
expect(result).to.not.be(array);
|
||||
expect(result).to.eql(['bar', 'baz', 'foo']);
|
||||
expect(array).to.eql(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
it('should not modify the order of the array other than matching prefix without modifying the original array', function() {
|
||||
const array = ['foo', 'bar', 'baz', 'qux', 'quux'];
|
||||
const result = sortPrefixFirst(array, 'b');
|
||||
expect(result).to.not.be(array);
|
||||
expect(result).to.eql(['bar', 'baz', 'foo', 'qux', 'quux']);
|
||||
expect(array).to.eql(['foo', 'bar', 'baz', 'qux', 'quux']);
|
||||
});
|
||||
|
||||
it('should sort objects by property if provided', function() {
|
||||
const array = [
|
||||
{ name: 'foo' },
|
||||
{ name: 'bar' },
|
||||
{ name: 'baz' },
|
||||
{ name: 'qux' },
|
||||
{ name: 'quux' },
|
||||
];
|
||||
const result = sortPrefixFirst(array, 'b', 'name');
|
||||
expect(result).to.not.be(array);
|
||||
expect(result).to.eql([
|
||||
{ name: 'bar' },
|
||||
{ name: 'baz' },
|
||||
{ name: 'foo' },
|
||||
{ name: 'qux' },
|
||||
{ name: 'quux' },
|
||||
]);
|
||||
expect(array).to.eql([
|
||||
{ name: 'foo' },
|
||||
{ name: 'bar' },
|
||||
{ name: 'baz' },
|
||||
{ name: 'qux' },
|
||||
{ name: 'quux' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle numbers', function() {
|
||||
const array = [1, 50, 5];
|
||||
const result = sortPrefixFirst(array, 5);
|
||||
expect(result).to.not.be(array);
|
||||
expect(result).to.eql([50, 5, 1]);
|
||||
});
|
||||
|
||||
it('should handle mixed case', function() {
|
||||
const array = ['Date Histogram', 'Histogram'];
|
||||
const prefix = 'histo';
|
||||
const result = sortPrefixFirst(array, prefix);
|
||||
expect(result).to.not.be(array);
|
||||
expect(result).to.eql(['Histogram', 'Date Histogram']);
|
||||
});
|
||||
});
|
141
src/legacy/ui/public/utils/collection.test.ts
Normal file
141
src/legacy/ui/public/utils/collection.test.ts
Normal file
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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 { move } from './collection';
|
||||
|
||||
describe('collection', () => {
|
||||
describe('move', () => {
|
||||
test('accepts previous from->to syntax', () => {
|
||||
const list = [1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1];
|
||||
|
||||
expect(list[3]).toBe(1);
|
||||
expect(list[8]).toBe(8);
|
||||
|
||||
move(list, 8, 3);
|
||||
|
||||
expect(list[8]).toBe(1);
|
||||
expect(list[3]).toBe(8);
|
||||
});
|
||||
|
||||
test('moves an object up based on a function callback', () => {
|
||||
const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1];
|
||||
|
||||
expect(list[4]).toBe(0);
|
||||
expect(list[5]).toBe(1);
|
||||
expect(list[6]).toBe(0);
|
||||
|
||||
move(list, 5, false, (v: any) => v === 0);
|
||||
|
||||
expect(list[4]).toBe(1);
|
||||
expect(list[5]).toBe(0);
|
||||
expect(list[6]).toBe(0);
|
||||
});
|
||||
|
||||
test('moves an object down based on a function callback', () => {
|
||||
const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1];
|
||||
|
||||
expect(list[4]).toBe(0);
|
||||
expect(list[5]).toBe(1);
|
||||
expect(list[6]).toBe(0);
|
||||
|
||||
move(list, 5, true, (v: any) => v === 0);
|
||||
|
||||
expect(list[4]).toBe(0);
|
||||
expect(list[5]).toBe(0);
|
||||
expect(list[6]).toBe(1);
|
||||
});
|
||||
|
||||
test('moves an object up based on a where callback', () => {
|
||||
const list = [
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 0 },
|
||||
{ v: 1 },
|
||||
{ v: 0 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
];
|
||||
|
||||
expect(list[4]).toHaveProperty('v', 0);
|
||||
expect(list[5]).toHaveProperty('v', 1);
|
||||
expect(list[6]).toHaveProperty('v', 0);
|
||||
|
||||
move(list, 5, false, { v: 0 });
|
||||
|
||||
expect(list[4]).toHaveProperty('v', 1);
|
||||
expect(list[5]).toHaveProperty('v', 0);
|
||||
expect(list[6]).toHaveProperty('v', 0);
|
||||
});
|
||||
|
||||
test('moves an object down based on a where callback', () => {
|
||||
const list = [
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 0 },
|
||||
{ v: 1 },
|
||||
{ v: 0 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
{ v: 1 },
|
||||
];
|
||||
|
||||
expect(list[4]).toHaveProperty('v', 0);
|
||||
expect(list[5]).toHaveProperty('v', 1);
|
||||
expect(list[6]).toHaveProperty('v', 0);
|
||||
|
||||
move(list, 5, true, { v: 0 });
|
||||
|
||||
expect(list[4]).toHaveProperty('v', 0);
|
||||
expect(list[5]).toHaveProperty('v', 0);
|
||||
expect(list[6]).toHaveProperty('v', 1);
|
||||
});
|
||||
|
||||
test('moves an object down based on a pluck callback', () => {
|
||||
const list = [
|
||||
{ id: 0, normal: true },
|
||||
{ id: 1, normal: true },
|
||||
{ id: 2, normal: true },
|
||||
{ id: 3, normal: true },
|
||||
{ id: 4, normal: true },
|
||||
{ id: 5, normal: false },
|
||||
{ id: 6, normal: true },
|
||||
{ id: 7, normal: true },
|
||||
{ id: 8, normal: true },
|
||||
{ id: 9, normal: true },
|
||||
];
|
||||
|
||||
expect(list[4]).toHaveProperty('id', 4);
|
||||
expect(list[5]).toHaveProperty('id', 5);
|
||||
expect(list[6]).toHaveProperty('id', 6);
|
||||
|
||||
move(list, 5, true, 'normal');
|
||||
|
||||
expect(list[4]).toHaveProperty('id', 4);
|
||||
expect(list[5]).toHaveProperty('id', 6);
|
||||
expect(list[6]).toHaveProperty('id', 5);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -33,10 +33,10 @@ import _ from 'lodash';
|
|||
* @return {array} - the objs argument
|
||||
*/
|
||||
export function move(
|
||||
objs: object[],
|
||||
objs: any[],
|
||||
obj: object | number,
|
||||
below: number | boolean,
|
||||
qualifier: (object: object, index: number) => any
|
||||
qualifier?: ((object: object, index: number) => any) | Record<string, any> | string
|
||||
): object[] {
|
||||
const origI = _.isNumber(obj) ? obj : objs.indexOf(obj);
|
||||
if (origI === -1) {
|
||||
|
@ -50,7 +50,7 @@ export function move(
|
|||
}
|
||||
|
||||
below = !!below;
|
||||
qualifier = _.callback(qualifier);
|
||||
qualifier = qualifier && _.callback(qualifier);
|
||||
|
||||
const above = !below;
|
||||
const finder = below ? _.findIndex : _.findLastIndex;
|
||||
|
@ -63,7 +63,7 @@ export function move(
|
|||
if (above && otherI >= origI) {
|
||||
return;
|
||||
}
|
||||
return !!qualifier(otherAgg, otherI);
|
||||
return Boolean(_.isFunction(qualifier) && qualifier(otherAgg, otherI));
|
||||
});
|
||||
|
||||
if (targetI === -1) {
|
||||
|
@ -74,68 +74,3 @@ export function move(
|
|||
objs.splice(targetI, 0, objs.splice(origI, 1)[0]);
|
||||
return objs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like _.groupBy, but allows specifying multiple groups for a
|
||||
* single object.
|
||||
*
|
||||
* organizeBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a')
|
||||
* // Object {1: Array[2], 2: Array[1], 3: Array[1], 4: Array[1]}
|
||||
*
|
||||
* _.groupBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a')
|
||||
* // Object {'1,2,3': Array[1], '1,4': Array[1]}
|
||||
*
|
||||
* @param {array} collection - the list of values to organize
|
||||
* @param {Function} callback - either a property name, or a callback.
|
||||
* @return {object}
|
||||
*/
|
||||
export function organizeBy(collection: object[], callback: (obj: object) => string | string) {
|
||||
const buckets: { [key: string]: object[] } = {};
|
||||
const prop = typeof callback === 'function' ? false : callback;
|
||||
|
||||
function add(key: string, obj: object) {
|
||||
if (!buckets[key]) {
|
||||
buckets[key] = [];
|
||||
}
|
||||
buckets[key].push(obj);
|
||||
}
|
||||
|
||||
_.each(collection, (obj: object) => {
|
||||
const keys = prop === false ? callback(obj) : obj[prop];
|
||||
|
||||
if (!Array.isArray(keys)) {
|
||||
add(keys, obj);
|
||||
return;
|
||||
}
|
||||
|
||||
let length = keys.length;
|
||||
while (length-- > 0) {
|
||||
add(keys[length], obj);
|
||||
}
|
||||
});
|
||||
|
||||
return buckets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Efficient and safe version of [].push(dest, source);
|
||||
*
|
||||
* @param {Array} source - the array to pull values from
|
||||
* @param {Array} dest - the array to push values into
|
||||
* @return {Array} dest
|
||||
*/
|
||||
export function pushAll(source: any[], dest: any[]): any[] {
|
||||
const start = dest.length;
|
||||
const adding = source.length;
|
||||
|
||||
// allocate - http://goo.gl/e2i0S0
|
||||
dest.length = start + adding;
|
||||
|
||||
// fill sparse positions
|
||||
let i = -1;
|
||||
while (++i < adding) {
|
||||
dest[start + i] = source[i];
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* 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 _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
|
||||
export function applyDiff(target, source) {
|
||||
const diff = {};
|
||||
|
||||
/**
|
||||
* Filter the private vars
|
||||
* @param {string} key The keys
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const filterPrivateAndMethods = function(obj) {
|
||||
return function(key) {
|
||||
if (_.isFunction(obj[key])) return false;
|
||||
if (key.charAt(0) === '$') return false;
|
||||
return key.charAt(0) !== '_';
|
||||
};
|
||||
};
|
||||
|
||||
const targetKeys = _.keys(target).filter(filterPrivateAndMethods(target));
|
||||
const sourceKeys = _.keys(source).filter(filterPrivateAndMethods(source));
|
||||
|
||||
// Find the keys to be removed
|
||||
diff.removed = _.difference(targetKeys, sourceKeys);
|
||||
|
||||
// Find the keys to be added
|
||||
diff.added = _.difference(sourceKeys, targetKeys);
|
||||
|
||||
// Find the keys that will be changed
|
||||
diff.changed = _.filter(sourceKeys, function(key) {
|
||||
return !angular.equals(target[key], source[key]);
|
||||
});
|
||||
|
||||
// Make a list of all the keys that are changing
|
||||
diff.keys = _.union(diff.changed, diff.removed, diff.added);
|
||||
|
||||
// Remove all the keys
|
||||
_.each(diff.removed, function(key) {
|
||||
delete target[key];
|
||||
});
|
||||
|
||||
// Assign the changed to the source to the target
|
||||
_.assign(target, _.pick(source, diff.changed));
|
||||
// Assign the added to the source to the target
|
||||
_.assign(target, _.pick(source, diff.added));
|
||||
|
||||
return diff;
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* 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 { greatestCommonDivisor, leastCommonMultiple } from './math';
|
||||
|
||||
describe('math utils', () => {
|
||||
describe('greatestCommonDivisor', () => {
|
||||
const tests: Array<[number, number, number]> = [
|
||||
[3, 5, 1],
|
||||
[30, 36, 6],
|
||||
[5, 1, 1],
|
||||
[9, 9, 9],
|
||||
[40, 20, 20],
|
||||
[3, 0, 3],
|
||||
[0, 5, 5],
|
||||
[0, 0, 0],
|
||||
[-9, -3, 3],
|
||||
[-24, 8, 8],
|
||||
[22, -7, 1],
|
||||
];
|
||||
|
||||
tests.map(([a, b, expected]) => {
|
||||
it(`should return ${expected} for greatestCommonDivisor(${a}, ${b})`, () => {
|
||||
expect(greatestCommonDivisor(a, b)).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('leastCommonMultiple', () => {
|
||||
const tests: Array<[number, number, number]> = [
|
||||
[3, 5, 15],
|
||||
[1, 1, 1],
|
||||
[5, 6, 30],
|
||||
[3, 9, 9],
|
||||
[8, 20, 40],
|
||||
[5, 5, 5],
|
||||
[0, 5, 0],
|
||||
[-4, -5, 20],
|
||||
[-2, -3, 6],
|
||||
[-8, 2, 8],
|
||||
[-8, 5, 40],
|
||||
];
|
||||
|
||||
tests.map(([a, b, expected]) => {
|
||||
it(`should return ${expected} for leastCommonMultiple(${a}, ${b})`, () => {
|
||||
expect(leastCommonMultiple(a, b)).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -21,7 +21,7 @@ import { TimeIntervalParam } from 'ui/vis/editors/config/types';
|
|||
import { AggConfig } from '../..';
|
||||
import { AggType } from '../../../agg_types';
|
||||
import { IndexPattern } from '../../../../../../plugins/data/public';
|
||||
import { leastCommonMultiple } from '../../../utils/math';
|
||||
import { leastCommonMultiple } from '../../lib/least_common_multiple';
|
||||
import { parseEsInterval } from '../../../../../core_plugins/data/public';
|
||||
import { leastCommonInterval } from '../../lib/least_common_interval';
|
||||
import { EditorConfig, EditorParamConfig, FixedParam, NumericIntervalParam } from './types';
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import dateMath from '@elastic/datemath';
|
||||
import { leastCommonMultiple } from '../../utils/math';
|
||||
import { leastCommonMultiple } from './least_common_multiple';
|
||||
import { parseEsInterval } from '../../../../core_plugins/data/public';
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,20 +17,26 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// TODO: This file is copied from src/legacy/utils/case_conversion.ts
|
||||
// because TS-imports from utils in ui are currently not possible.
|
||||
// When the build process is updated, this file can be removed
|
||||
import { leastCommonMultiple } from './least_common_multiple';
|
||||
|
||||
import _ from 'lodash';
|
||||
describe('leastCommonMultiple', () => {
|
||||
const tests: Array<[number, number, number]> = [
|
||||
[3, 5, 15],
|
||||
[1, 1, 1],
|
||||
[5, 6, 30],
|
||||
[3, 9, 9],
|
||||
[8, 20, 40],
|
||||
[5, 5, 5],
|
||||
[0, 5, 0],
|
||||
[-4, -5, 20],
|
||||
[-2, -3, 6],
|
||||
[-8, 2, 8],
|
||||
[-8, 5, 40],
|
||||
];
|
||||
|
||||
export function keysToSnakeCaseShallow(object: Record<string, any>) {
|
||||
return _.mapKeys(object, (value, key) => {
|
||||
return _.snakeCase(key);
|
||||
tests.map(([a, b, expected]) => {
|
||||
test(`should return ${expected} for leastCommonMultiple(${a}, ${b})`, () => {
|
||||
expect(leastCommonMultiple(a, b)).toBe(expected);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function keysToCamelCaseShallow(object: Record<string, any>) {
|
||||
return _.mapKeys(object, (value, key) => {
|
||||
return _.camelCase(key);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -24,8 +24,10 @@
|
|||
* This method does not properly work for fractional (non integer) numbers. If you
|
||||
* pass in fractional numbers there usually will be an output, but that's not necessarily
|
||||
* the greatest common divisor of those two numbers.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
export function greatestCommonDivisor(a: number, b: number): number {
|
||||
function greatestCommonDivisor(a: number, b: number): number {
|
||||
return a === 0 ? Math.abs(b) : greatestCommonDivisor(b % a, a);
|
||||
}
|
||||
|
||||
|
@ -36,6 +38,8 @@ export function greatestCommonDivisor(a: number, b: number): number {
|
|||
* Since this calculation suffers from rounding issues in decimal values, this method
|
||||
* won't work for passing in fractional (non integer) numbers. It will return a value,
|
||||
* but that value won't necessarily be the mathematical correct least common multiple.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function leastCommonMultiple(a: number, b: number): number {
|
||||
return Math.abs((a * b) / greatestCommonDivisor(a, b));
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* 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 { keysToCamelCaseShallow, keysToSnakeCaseShallow } from './case_conversion';
|
||||
|
||||
describe('keysToSnakeCaseShallow', () => {
|
||||
it("should convert all of an object's keys to snake case", () => {
|
||||
const result = keysToSnakeCaseShallow({
|
||||
camelCase: 'camel_case',
|
||||
'kebab-case': 'kebab_case',
|
||||
snake_case: 'snake_case',
|
||||
});
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"camel_case": "camel_case",
|
||||
"kebab_case": "kebab_case",
|
||||
"snake_case": "snake_case",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('keysToCamelCaseShallow', () => {
|
||||
it("should convert all of an object's keys to camel case", () => {
|
||||
const result = keysToCamelCaseShallow({
|
||||
camelCase: 'camelCase',
|
||||
'kebab-case': 'kebabCase',
|
||||
snake_case: 'snakeCase',
|
||||
});
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"camelCase": "camelCase",
|
||||
"kebabCase": "kebabCase",
|
||||
"snakeCase": "snakeCase",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -18,3 +18,4 @@
|
|||
*/
|
||||
|
||||
export { shortenDottedString } from './shorten_dotted_string';
|
||||
export { parseInterval } from './parse_interval';
|
||||
|
|
119
src/plugins/data/common/utils/parse_interval.test.ts
Normal file
119
src/plugins/data/common/utils/parse_interval.test.ts
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* 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 { Duration, unitOfTime } from 'moment';
|
||||
import { parseInterval } from './parse_interval';
|
||||
|
||||
const validateDuration = (duration: Duration | null, unit: unitOfTime.Base, value: number) => {
|
||||
expect(duration).toBeDefined();
|
||||
|
||||
if (duration) {
|
||||
expect(duration.as(unit)).toBe(value);
|
||||
}
|
||||
};
|
||||
|
||||
describe('parseInterval', () => {
|
||||
describe('integer', () => {
|
||||
test('should correctly parse 1d interval', () => {
|
||||
validateDuration(parseInterval('1d'), 'd', 1);
|
||||
});
|
||||
|
||||
test('should correctly parse 2y interval', () => {
|
||||
validateDuration(parseInterval('2y'), 'y', 2);
|
||||
});
|
||||
|
||||
test('should correctly parse 5M interval', () => {
|
||||
validateDuration(parseInterval('5M'), 'M', 5);
|
||||
});
|
||||
|
||||
test('should correctly parse 5m interval', () => {
|
||||
validateDuration(parseInterval('5m'), 'm', 5);
|
||||
});
|
||||
|
||||
test('should correctly parse 250ms interval', () => {
|
||||
validateDuration(parseInterval('250ms'), 'ms', 250);
|
||||
});
|
||||
|
||||
test('should correctly parse 100s interval', () => {
|
||||
validateDuration(parseInterval('100s'), 's', 100);
|
||||
});
|
||||
|
||||
test('should correctly parse 23d interval', () => {
|
||||
validateDuration(parseInterval('23d'), 'd', 23);
|
||||
});
|
||||
|
||||
test('should correctly parse 52w interval', () => {
|
||||
validateDuration(parseInterval('52w'), 'w', 52);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fractional interval', () => {
|
||||
test('should correctly parse fractional 2.35y interval', () => {
|
||||
validateDuration(parseInterval('2.35y'), 'y', 2.35);
|
||||
});
|
||||
|
||||
test('should correctly parse fractional 1.5w interval', () => {
|
||||
validateDuration(parseInterval('1.5w'), 'w', 1.5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('less than 1', () => {
|
||||
test('should correctly bubble up 0.5h interval which are less than 1', () => {
|
||||
validateDuration(parseInterval('0.5h'), 'm', 30);
|
||||
});
|
||||
|
||||
test('should correctly bubble up 0.5d interval which are less than 1', () => {
|
||||
validateDuration(parseInterval('0.5d'), 'h', 12);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unit in an interval only', () => {
|
||||
test('should correctly parse ms interval', () => {
|
||||
validateDuration(parseInterval('ms'), 'ms', 1);
|
||||
});
|
||||
|
||||
test('should correctly parse d interval', () => {
|
||||
validateDuration(parseInterval('d'), 'd', 1);
|
||||
});
|
||||
|
||||
test('should correctly parse m interval', () => {
|
||||
validateDuration(parseInterval('m'), 'm', 1);
|
||||
});
|
||||
|
||||
test('should correctly parse y interval', () => {
|
||||
validateDuration(parseInterval('y'), 'y', 1);
|
||||
});
|
||||
|
||||
test('should correctly parse M interval', () => {
|
||||
validateDuration(parseInterval('M'), 'M', 1);
|
||||
});
|
||||
});
|
||||
|
||||
test('should return null for an invalid interval', () => {
|
||||
let duration = parseInterval('');
|
||||
expect(duration).toBeNull();
|
||||
|
||||
// @ts-ignore
|
||||
duration = parseInterval(null);
|
||||
expect(duration).toBeNull();
|
||||
|
||||
duration = parseInterval('234asdf');
|
||||
expect(duration).toBeNull();
|
||||
});
|
||||
});
|
|
@ -17,14 +17,14 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { find } from 'lodash';
|
||||
import moment, { unitOfTime } from 'moment';
|
||||
import dateMath from '@elastic/datemath';
|
||||
|
||||
// Assume interval is in the form (value)(unit), such as "1h"
|
||||
const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + dateMath.units.join('|') + ')$');
|
||||
|
||||
export function parseInterval(interval) {
|
||||
export function parseInterval(interval: string): moment.Duration | null {
|
||||
const matches = String(interval)
|
||||
.trim()
|
||||
.match(INTERVAL_STRING_RE);
|
||||
|
@ -33,7 +33,7 @@ export function parseInterval(interval) {
|
|||
|
||||
try {
|
||||
const value = parseFloat(matches[1]) || 1;
|
||||
const unit = matches[2];
|
||||
const unit = matches[2] as unitOfTime.Base;
|
||||
|
||||
const duration = moment.duration(value, unit);
|
||||
|
||||
|
@ -44,9 +44,10 @@ export function parseInterval(interval) {
|
|||
// adding 0.5 days until we hit the end date. However, since there is a bug in moment, when you add 0.5 days to
|
||||
// the start date, you get the same exact date (instead of being ahead by 12 hours). So instead of returning
|
||||
// a duration corresponding to 0.5 hours, we return a duration corresponding to 12 hours.
|
||||
const selectedUnit = _.find(dateMath.units, function(unit) {
|
||||
return Math.abs(duration.as(unit)) >= 1;
|
||||
});
|
||||
const selectedUnit = find(
|
||||
dateMath.units,
|
||||
u => Math.abs(duration.as(u)) >= 1
|
||||
) as unitOfTime.Base;
|
||||
|
||||
return moment.duration(duration.as(selectedUnit), selectedUnit);
|
||||
} catch (e) {
|
|
@ -90,6 +90,8 @@ export {
|
|||
castEsToKbnFieldTypeName,
|
||||
getKbnFieldType,
|
||||
getKbnTypeNames,
|
||||
// utils
|
||||
parseInterval,
|
||||
} from '../common';
|
||||
|
||||
// Export plugin after all other imports
|
||||
|
|
|
@ -47,6 +47,8 @@ export {
|
|||
// timefilter
|
||||
RefreshInterval,
|
||||
TimeRange,
|
||||
// utils
|
||||
parseInterval,
|
||||
} from '../common';
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import Bluebird from 'bluebird';
|
||||
import { keyMap } from 'ui/utils/key_map';
|
||||
import { keyMap } from 'ui/directives/key_map';
|
||||
const reverseKeyMap = _.mapValues(_.invert(keyMap), _.ary(_.parseInt, 1));
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import React from 'react';
|
||||
import { flatten } from 'lodash';
|
||||
import { escapeKuery } from './escape_kuery';
|
||||
import { sortPrefixFirst } from 'ui/utils/sort_prefix_first';
|
||||
import { sortPrefixFirst } from './sort_prefix_first';
|
||||
import { isFilterable } from '../../../../../../src/plugins/data/public';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { sortPrefixFirst } from './sort_prefix_first';
|
||||
|
||||
describe('sortPrefixFirst', () => {
|
||||
test('should return the original unmodified array if no prefix is provided', () => {
|
||||
const array = ['foo', 'bar', 'baz'];
|
||||
const result = sortPrefixFirst(array);
|
||||
|
||||
expect(result).toBe(array);
|
||||
expect(result).toEqual(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
test('should sort items that match the prefix first without modifying the original array', () => {
|
||||
const array = ['foo', 'bar', 'baz'];
|
||||
const result = sortPrefixFirst(array, 'b');
|
||||
|
||||
expect(result).not.toBe(array);
|
||||
expect(result).toEqual(['bar', 'baz', 'foo']);
|
||||
expect(array).toEqual(['foo', 'bar', 'baz']);
|
||||
});
|
||||
|
||||
test('should not modify the order of the array other than matching prefix without modifying the original array', () => {
|
||||
const array = ['foo', 'bar', 'baz', 'qux', 'quux'];
|
||||
const result = sortPrefixFirst(array, 'b');
|
||||
|
||||
expect(result).not.toBe(array);
|
||||
expect(result).toEqual(['bar', 'baz', 'foo', 'qux', 'quux']);
|
||||
expect(array).toEqual(['foo', 'bar', 'baz', 'qux', 'quux']);
|
||||
});
|
||||
|
||||
test('should sort objects by property if provided', () => {
|
||||
const array = [
|
||||
{ name: 'foo' },
|
||||
{ name: 'bar' },
|
||||
{ name: 'baz' },
|
||||
{ name: 'qux' },
|
||||
{ name: 'quux' },
|
||||
];
|
||||
const result = sortPrefixFirst(array, 'b', 'name');
|
||||
|
||||
expect(result).not.toBe(array);
|
||||
expect(result).toEqual([
|
||||
{ name: 'bar' },
|
||||
{ name: 'baz' },
|
||||
{ name: 'foo' },
|
||||
{ name: 'qux' },
|
||||
{ name: 'quux' },
|
||||
]);
|
||||
expect(array).toEqual([
|
||||
{ name: 'foo' },
|
||||
{ name: 'bar' },
|
||||
{ name: 'baz' },
|
||||
{ name: 'qux' },
|
||||
{ name: 'quux' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('should handle numbers', () => {
|
||||
const array = [1, 50, 5];
|
||||
const result = sortPrefixFirst(array, 5);
|
||||
|
||||
expect(result).not.toBe(array);
|
||||
expect(result).toEqual([50, 5, 1]);
|
||||
});
|
||||
|
||||
test('should handle mixed case', () => {
|
||||
const array = ['Date Histogram', 'Histogram'];
|
||||
const prefix = 'histo';
|
||||
const result = sortPrefixFirst(array, prefix);
|
||||
|
||||
expect(result).not.toBe(array);
|
||||
expect(result).toEqual(['Histogram', 'Date Histogram']);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { partition } from 'lodash';
|
||||
|
||||
export function sortPrefixFirst(array: any[], prefix?: string | number, property?: string): any[] {
|
||||
if (!prefix) {
|
||||
return array;
|
||||
}
|
||||
const lowerCasePrefix = ('' + prefix).toLowerCase();
|
||||
|
||||
const partitions = partition(array, entry => {
|
||||
const value = ('' + (property ? entry[property] : entry)).toLowerCase();
|
||||
return value.startsWith(lowerCasePrefix);
|
||||
});
|
||||
return [...partitions[0], ...partitions[1]];
|
||||
}
|
Loading…
Reference in a new issue