mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
remove remaining idx usage (#52354)
* remove remaining idx usage * handle possibly undefined value * update NOTICE.txt
This commit is contained in:
parent
a863dca9c2
commit
3293ede421
28 changed files with 43 additions and 1271 deletions
26
NOTICE.txt
26
NOTICE.txt
|
@ -186,32 +186,6 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---
|
||||
This product includes code that is based on facebookincubator/idx, which was
|
||||
available under a "MIT" license.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013-present, Facebook, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
This product includes code that was extracted from angular@1.3.
|
||||
Original license:
|
||||
|
|
|
@ -28,6 +28,11 @@ module.exports = {
|
|||
to: false,
|
||||
disallowedMessage: `Don't use 'mkdirp', use the new { recursive: true } option of Fs.mkdir instead`
|
||||
},
|
||||
{
|
||||
from: '@kbn/elastic-idx',
|
||||
to: false,
|
||||
disallowedMessage: `Don't use idx(), use optional chaining syntax instead https://ela.st/optchain`
|
||||
},
|
||||
{
|
||||
from: 'x-pack',
|
||||
toRelative: 'x-pack',
|
||||
|
|
|
@ -35,13 +35,6 @@ const plugins = [
|
|||
// Need this since we are using TypeScript 3.7+
|
||||
require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'),
|
||||
];
|
||||
const isTestEnv = process.env.BABEL_ENV === 'test' || process.env.NODE_ENV === 'test';
|
||||
|
||||
// Only load the idx plugin in non-test environments, since it conflicts with
|
||||
// Jest's coverage mapping.
|
||||
if (!isTestEnv) {
|
||||
plugins.push(require.resolve('@kbn/elastic-idx/babel'));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
presets: [require.resolve('@babel/preset-typescript'), require.resolve('@babel/preset-react')],
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
"@babel/preset-env": "^7.5.5",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@kbn/elastic-idx": "1.0.0",
|
||||
"babel-plugin-add-module-exports": "^1.0.2",
|
||||
"babel-plugin-filter-imports": "^3.0.0",
|
||||
"babel-plugin-transform-define": "^1.3.1",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
/tsconfig.json
|
||||
/src
|
||||
/babel/index.test.js
|
|
@ -1,76 +0,0 @@
|
|||
Kibana elastic-idx Library
|
||||
==========================
|
||||
|
||||
The `@kbn/elastic-idx` package provides the `idx` function used for optional
|
||||
chaining. Currently, the optional chaining draft is in stage 1, making it too
|
||||
uncertain to add syntax support within Kibana. Other optional chaining
|
||||
libraries require the Proxy object to be polyfilled for browser support,
|
||||
however, this polyfill is not fully supported across all browsers that Kibana
|
||||
requires. The facebookincubator `idx` project
|
||||
(https://github.com/facebookincubator/idx) provides an answer to this with a
|
||||
specific implementation that is understood by TypeScript so that type
|
||||
information does not get lost (unlike lodash get) The `@kbn/elastic-idx`
|
||||
library makes use the `idx` idiom but differs in the way null values within the
|
||||
property chain are handled.
|
||||
|
||||
Similar to the facebookincubator `idx` project, `@kbn/elastic-idx` also
|
||||
provides the Babel plugin to transform `idx()` function calls into the expanded
|
||||
form. This Babel plugin was based off the facebookincubator `idx` Babel
|
||||
plugin, since the invocation syntax is almost identical, but the transformed
|
||||
code differs to match how the `@kbn/elastic-idx` library treats null values.
|
||||
|
||||
App Usage
|
||||
----------
|
||||
Within Kibana, `@kbn/elastic-idx` can be imported and used in any JavaScript or
|
||||
TypeScript project:
|
||||
|
||||
```
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
|
||||
const obj0 = { a: { b: { c: { d: 'iamdefined' } } } };
|
||||
const obj1 = { a: { b: null } };
|
||||
|
||||
idx(obj0, _ => _.a.b.c.d); // returns 'iamdefined'
|
||||
idx(obj1, _ => _.a.b.c.e); // returns undefined
|
||||
idx(obj1, _ => _.a.b); // returns null
|
||||
```
|
||||
|
||||
Build Optimization
|
||||
-------------------
|
||||
Similar to the facebookincubator `idx` project, it is NOT RECOMMENDED to use
|
||||
idx in shipped app code. The implementation details which make
|
||||
`@kbn/elastic-idx` possible comes at a non-negligible performance cost. This
|
||||
usually isn't noticable during development, but for production builds, it is
|
||||
recommended to transform idx calls into native, expanded form JS. Use the
|
||||
plugin `@kbn/elastic-idx/babel` within your Babel configuration:
|
||||
|
||||
```
|
||||
{ "plugins": [ "@kbn/elastic-idx/babel" ] }
|
||||
```
|
||||
|
||||
The resulting Babel transforms the following:
|
||||
|
||||
```
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
const obj = { a: { b: { c: { d: 'iamdefined' } } } };
|
||||
idx(obj, _ => _.a.b.c.d);
|
||||
```
|
||||
|
||||
into this:
|
||||
|
||||
```
|
||||
obj != null &&
|
||||
obj.a != null &&
|
||||
obj.a.b != null &&
|
||||
obj.a.b.c != null ?
|
||||
obj.a.b.c.d : undefined
|
||||
```
|
||||
|
||||
Note that this also removes the import statement from the source code, since it
|
||||
no longer needs to be bundled.
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Tests can be run with `npm test`. This includes "functional" tests that
|
||||
transform and evaluate idx calls.
|
|
@ -1,304 +0,0 @@
|
|||
/* eslint-disable @kbn/eslint/require-license-header */
|
||||
|
||||
/* @notice
|
||||
* This product includes code that is based on facebookincubator/idx, which was
|
||||
* available under a "MIT" license.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/* eslint strict: 0, new-cap: 0 */
|
||||
|
||||
'use strict';
|
||||
module.exports = context => {
|
||||
const t = context.types;
|
||||
|
||||
const idxRe = /\bidx\b/;
|
||||
|
||||
function checkIdxArguments(file, node) {
|
||||
const args = node.arguments;
|
||||
if (args.length !== 2) {
|
||||
throw file.buildCodeFrameError(node, 'The `idx` function takes exactly two arguments.');
|
||||
}
|
||||
const arrowFunction = args[1];
|
||||
if (!t.isArrowFunctionExpression(arrowFunction)) {
|
||||
throw file.buildCodeFrameError(
|
||||
arrowFunction,
|
||||
'The second argument supplied to `idx` must be an arrow function.'
|
||||
);
|
||||
}
|
||||
if (!t.isExpression(arrowFunction.body)) {
|
||||
throw file.buildCodeFrameError(
|
||||
arrowFunction.body,
|
||||
'The body of the arrow function supplied to `idx` must be a single ' +
|
||||
'expression (without curly braces).'
|
||||
);
|
||||
}
|
||||
if (arrowFunction.params.length !== 1) {
|
||||
throw file.buildCodeFrameError(
|
||||
arrowFunction.params[2] || arrowFunction,
|
||||
'The arrow function supplied to `idx` must take exactly one parameter.'
|
||||
);
|
||||
}
|
||||
const input = arrowFunction.params[0];
|
||||
if (!t.isIdentifier(input)) {
|
||||
throw file.buildCodeFrameError(
|
||||
arrowFunction.params[0],
|
||||
'The parameter supplied to `idx` must be an identifier.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function checkIdxBindingNode(file, node) {
|
||||
if (t.isImportDeclaration(node)) {
|
||||
// E.g. `import '...'`
|
||||
if (node.specifiers.length === 0) {
|
||||
throw file.buildCodeFrameError(node, 'The idx import must have a value.');
|
||||
}
|
||||
// E.g. `import A, {B} from '...'`
|
||||
// `import A, * as B from '...'`
|
||||
// `import {A, B} from '...'`
|
||||
if (node.specifiers.length > 1) {
|
||||
throw file.buildCodeFrameError(
|
||||
node.specifiers[1],
|
||||
'The idx import must be a single specifier.'
|
||||
);
|
||||
}
|
||||
// `importKind` is not a property unless flow syntax is enabled.
|
||||
// On specifiers, `importKind` is not "value" when it's not a type, it's
|
||||
// `null`.
|
||||
// E.g. `import type {...} from '...'`
|
||||
// `import typeof {...} from '...'`
|
||||
// `import {type ...} from '...'`.
|
||||
// `import {typeof ...} from '...'`
|
||||
if (
|
||||
node.importKind === 'type' ||
|
||||
node.importKind === 'typeof' ||
|
||||
node.specifiers[0].importKind === 'type' ||
|
||||
node.specifiers[0].importKind === 'typeof'
|
||||
) {
|
||||
throw file.buildCodeFrameError(node, 'The idx import must be a value import.');
|
||||
}
|
||||
} else if (t.isVariableDeclarator(node)) {
|
||||
// E.g. var {idx} or var [idx]
|
||||
if (!t.isIdentifier(node.id)) {
|
||||
throw file.buildCodeFrameError(
|
||||
node.specifiers[0],
|
||||
'The idx declaration must be an identifier.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UnsupportedNodeTypeError extends Error {
|
||||
constructor(node, ...params) {
|
||||
super(`Node type is not supported: ${node.type}`, ...params);
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, UnsupportedNodeTypeError);
|
||||
}
|
||||
|
||||
this.name = 'UnsupportedNodeTypeError';
|
||||
}
|
||||
}
|
||||
|
||||
function getDeepProperties(node, properties = [], computedProperties = new Set()) {
|
||||
if (t.isMemberExpression(node)) {
|
||||
if (node.computed) {
|
||||
computedProperties.add(node.property);
|
||||
}
|
||||
return getDeepProperties(node.object, [node.property, ...properties], computedProperties);
|
||||
} else if (t.isIdentifier(node)) {
|
||||
return [[node, ...properties], computedProperties];
|
||||
}
|
||||
|
||||
throw new UnsupportedNodeTypeError(node);
|
||||
}
|
||||
|
||||
function buildMemberChain(properties, computedProperties) {
|
||||
if (properties.length > 1) {
|
||||
const lead = properties.slice(0, properties.length - 1);
|
||||
const last = properties[properties.length - 1];
|
||||
return t.MemberExpression(
|
||||
buildMemberChain(lead, computedProperties),
|
||||
last,
|
||||
computedProperties.has(last)
|
||||
);
|
||||
} else if (properties.length === 1) {
|
||||
return properties[0];
|
||||
}
|
||||
return t.identifier('undefined');
|
||||
}
|
||||
|
||||
function buildExpandedMemberNullChecks(
|
||||
leadingProperties = [],
|
||||
trailingProperties = [],
|
||||
computedProperties
|
||||
) {
|
||||
const propertyChainNullCheck = t.BinaryExpression(
|
||||
'!=',
|
||||
buildMemberChain(leadingProperties, computedProperties),
|
||||
t.NullLiteral()
|
||||
);
|
||||
|
||||
if (trailingProperties.length <= 1) {
|
||||
return propertyChainNullCheck;
|
||||
}
|
||||
|
||||
const [headTrailingProperty, ...tailProperties] = trailingProperties;
|
||||
|
||||
return t.LogicalExpression(
|
||||
'&&',
|
||||
propertyChainNullCheck,
|
||||
buildExpandedMemberNullChecks(
|
||||
[...leadingProperties, headTrailingProperty],
|
||||
tailProperties,
|
||||
computedProperties
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function buildExpandedMemberAccess(node, state) {
|
||||
let baseNode;
|
||||
let properties;
|
||||
let computedProperties;
|
||||
|
||||
try {
|
||||
[[baseNode, ...properties], computedProperties] = getDeepProperties(node);
|
||||
} catch (error) {
|
||||
if (error instanceof UnsupportedNodeTypeError) {
|
||||
throw state.file.buildCodeFrameError(
|
||||
node,
|
||||
'idx callbacks may only access properties on the callback parameter.'
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (baseNode.name !== state.base.name) {
|
||||
throw state.file.buildCodeFrameError(
|
||||
node,
|
||||
'The parameter of the arrow function supplied to `idx` must match ' +
|
||||
'the base of the body expression.'
|
||||
);
|
||||
}
|
||||
return t.ConditionalExpression(
|
||||
buildExpandedMemberNullChecks([state.input], properties, computedProperties),
|
||||
buildMemberChain([state.input, ...properties], computedProperties),
|
||||
t.identifier('undefined')
|
||||
);
|
||||
}
|
||||
|
||||
function visitIdxCallExpression(path, state) {
|
||||
const node = path.node;
|
||||
checkIdxArguments(state.file, node);
|
||||
const replacement = buildExpandedMemberAccess(node.arguments[1].body, {
|
||||
file: state.file,
|
||||
input: node.arguments[0],
|
||||
base: node.arguments[1].params[0],
|
||||
});
|
||||
path.replaceWith(replacement);
|
||||
}
|
||||
|
||||
function isIdxImportOrRequire(node, name) {
|
||||
if (t.isImportDeclaration(node)) {
|
||||
if (name instanceof RegExp) {
|
||||
return name.test(node.source.value);
|
||||
} else {
|
||||
return t.isStringLiteral(node.source, { value: name });
|
||||
}
|
||||
} else if (t.isVariableDeclarator(node)) {
|
||||
return (
|
||||
t.isCallExpression(node.init) &&
|
||||
t.isIdentifier(node.init.callee, { name: 'require' }) &&
|
||||
(name instanceof RegExp
|
||||
? name.test(node.init.arguments[0].value)
|
||||
: t.isLiteral(node.init.arguments[0], { value: name }))
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const declareVisitor = {
|
||||
'ImportDeclaration|VariableDeclarator'(path, state) {
|
||||
if (!isIdxImportOrRequire(path.node, state.importName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkIdxBindingNode(state.file, path.node);
|
||||
|
||||
const bindingName = t.isImportDeclaration(path.node)
|
||||
? path.node.specifiers[0].local.name
|
||||
: path.node.id.name;
|
||||
const idxBinding = path.scope.getOwnBinding(bindingName);
|
||||
|
||||
idxBinding.constantViolations.forEach(refPath => {
|
||||
throw state.file.buildCodeFrameError(refPath.node, '`idx` cannot be redefined.');
|
||||
});
|
||||
|
||||
let didTransform = false;
|
||||
let didSkip = false;
|
||||
|
||||
// Traverse the references backwards to process inner calls before
|
||||
// outer calls.
|
||||
idxBinding.referencePaths
|
||||
.slice()
|
||||
.reverse()
|
||||
.forEach(refPath => {
|
||||
if (refPath.node === idxBinding.node) {
|
||||
// Do nothing...
|
||||
} else if (refPath.parentPath.isMemberExpression()) {
|
||||
visitIdxCallExpression(refPath.parentPath.parentPath, state);
|
||||
didTransform = true;
|
||||
} else if (refPath.parentPath.isCallExpression()) {
|
||||
visitIdxCallExpression(refPath.parentPath, state);
|
||||
didTransform = true;
|
||||
} else {
|
||||
// Should this throw?
|
||||
didSkip = true;
|
||||
}
|
||||
});
|
||||
if (didTransform && !didSkip) {
|
||||
path.remove();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
visitor: {
|
||||
Program(path, state) {
|
||||
const importName = state.opts.importName || '@kbn/elastic-idx';
|
||||
// If there can't reasonably be an idx call, exit fast.
|
||||
if (importName !== '@kbn/elastic-idx' || idxRe.test(state.file.code)) {
|
||||
// We're very strict about the shape of idx. Some transforms, like
|
||||
// "babel-plugin-transform-async-to-generator", will convert arrow
|
||||
// functions inside async functions into regular functions. So we do
|
||||
// our transformation before any one else interferes.
|
||||
const newState = { file: state.file, importName };
|
||||
path.traverse(declareVisitor, newState);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
|
@ -1,694 +0,0 @@
|
|||
/* eslint-disable @kbn/eslint/require-license-header */
|
||||
|
||||
/* @notice
|
||||
* This product includes code that is based on facebookincubator/idx, which was
|
||||
* available under a "MIT" license.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
'use strict'; // eslint-disable-line strict
|
||||
|
||||
jest.autoMockOff();
|
||||
|
||||
const { transformSync: babelTransform } = require('@babel/core');
|
||||
const babelPluginIdx = require('./index');
|
||||
const transformAsyncToGenerator = require('@babel/plugin-transform-async-to-generator');
|
||||
const vm = require('vm');
|
||||
|
||||
function transform(source, plugins, options) {
|
||||
return babelTransform(source, {
|
||||
plugins: plugins || [[babelPluginIdx, options]],
|
||||
babelrc: false,
|
||||
highlightCode: false,
|
||||
}).code;
|
||||
}
|
||||
|
||||
const asyncToGeneratorHelperCode = `
|
||||
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
||||
try {
|
||||
var info = gen[key](arg);
|
||||
var value = info.value;
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
if (info.done) {
|
||||
resolve(value);
|
||||
} else {
|
||||
Promise.resolve(value).then(_next, _throw);
|
||||
}
|
||||
}
|
||||
|
||||
function _asyncToGenerator(fn) {
|
||||
return function() {
|
||||
var self = this,
|
||||
args = arguments;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var gen = fn.apply(self, args);
|
||||
|
||||
function _next(value) {
|
||||
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
||||
}
|
||||
|
||||
function _throw(err) {
|
||||
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
||||
}
|
||||
_next(undefined);
|
||||
});
|
||||
};
|
||||
}
|
||||
`;
|
||||
|
||||
function stringByTrimmingSpaces(string) {
|
||||
return string.replace(/\s+/g, '');
|
||||
}
|
||||
|
||||
expect.extend({
|
||||
toTransformInto: (input, expected) => {
|
||||
const plugins = typeof input === 'string' ? null : input.plugins;
|
||||
const options = typeof input === 'string' ? undefined : input.options;
|
||||
const code = typeof input === 'string' ? input : input.code;
|
||||
const actual = transform(code, plugins, options);
|
||||
const pass = stringByTrimmingSpaces(actual) === stringByTrimmingSpaces(expected);
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
'Expected input to transform into:\n' + expected + '\n' + 'Instead, got:\n' + actual,
|
||||
};
|
||||
},
|
||||
toThrowTransformError: (input, expected) => {
|
||||
try {
|
||||
const plugins = typeof input === 'string' ? null : input.plugins;
|
||||
const options = typeof input === 'string' ? undefined : input.options;
|
||||
const code = typeof input === 'string' ? input : input.code;
|
||||
transform(code, plugins, options);
|
||||
} catch (error) {
|
||||
const actual = /^.+:\s*(.*)/.exec(error.message)[1]; // Strip "undefined: " and code snippet
|
||||
return {
|
||||
pass: actual === expected,
|
||||
message: () =>
|
||||
'Expected transform to throw "' + expected + '", but instead ' + 'got "' + actual + '".',
|
||||
};
|
||||
}
|
||||
return {
|
||||
pass: false,
|
||||
message: () => 'Expected transform to throw "' + expected + '".',
|
||||
};
|
||||
},
|
||||
toReturn: (input, expected) => {
|
||||
const code = transform(input, undefined);
|
||||
const actual = vm.runInNewContext(code);
|
||||
return {
|
||||
pass: actual === expected,
|
||||
message: () => 'Expected "' + expected + '" but got "' + actual + '".',
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
describe('kbn-babel-plugin-apm-idx', () => {
|
||||
it('transforms member expressions', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, _ => _.b.c.d.e);
|
||||
`).toTransformInto(`
|
||||
base != null && base.b != null && base.b.c != null && base.b.c.d != null
|
||||
? base.b.c.d.e
|
||||
: undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws on call expressions', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, _ => _.b.c(...foo)().d(bar, null, [...baz]));
|
||||
`).toThrowTransformError('idx callbacks may only access properties on the callback parameter.');
|
||||
});
|
||||
|
||||
it('transforms bracket notation', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, _ => _["b"][0][c + d]);
|
||||
`).toTransformInto(`
|
||||
base != null && base["b"] != null && base["b"][0] != null ? base["b"][0][c + d] : undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws on bracket notation call expressions', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, _ => _["b"](...foo)()[0][c + d](bar, null, [...baz]));
|
||||
`).toThrowTransformError('idx callbacks may only access properties on the callback parameter.');
|
||||
});
|
||||
|
||||
it('transforms combination of both member access notations', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, _ => _.a["b"].c[d[e[f]]].g);
|
||||
`).toTransformInto(`
|
||||
base != null && base.a != null && base.a["b"] != null && base.a["b"].c != null && base.a["b"].c[d[e[f]]] != null
|
||||
? base.a["b"].c[d[e[f]]].g
|
||||
: undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('transforms if the base is an expression', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(this.props.base[5], _ => _.property);
|
||||
`).toTransformInto(`
|
||||
this.props.base[5] != null ? this.props.base[5].property : undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws if the arrow function has more than one param', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, (a, b) => _.property);
|
||||
`).toThrowTransformError(
|
||||
'The arrow function supplied to `idx` must take exactly one parameter.'
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if the arrow function has an invalid base', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, a => b.property)
|
||||
`).toThrowTransformError(
|
||||
'The parameter of the arrow function supplied to `idx` must match the ' +
|
||||
'base of the body expression.'
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if the arrow function expression has non-properties/methods', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, _ => (_.a++).b.c);
|
||||
`).toThrowTransformError('idx callbacks may only access properties on the callback parameter.');
|
||||
});
|
||||
|
||||
it('throws if the body of the arrow function is not an expression', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, _ => {})
|
||||
`).toThrowTransformError(
|
||||
'The body of the arrow function supplied to `idx` must be a single ' +
|
||||
'expression (without curly braces).'
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores non-function call idx', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
result = idx;
|
||||
`).toTransformInto(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
result = idx;
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws if idx is called with zero arguments', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx();
|
||||
`).toThrowTransformError('The `idx` function takes exactly two arguments.');
|
||||
});
|
||||
|
||||
it('throws if idx is called with one argument', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(1);
|
||||
`).toThrowTransformError('The `idx` function takes exactly two arguments.');
|
||||
});
|
||||
|
||||
it('throws if idx is called with three arguments', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(1, 2, 3);
|
||||
`).toThrowTransformError('The `idx` function takes exactly two arguments.');
|
||||
});
|
||||
|
||||
it('transforms idx calls as part of another expressions', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
paddingStatement();
|
||||
a = idx(base, _ => _.b[c]);
|
||||
`).toTransformInto(`
|
||||
paddingStatement();
|
||||
a = base != null && base.b != null ? base.b[c] : undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('transforms nested idx calls', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(
|
||||
idx(
|
||||
idx(base, _ => _.a.b),
|
||||
_ => _.c.d
|
||||
),
|
||||
_ => _.e.f
|
||||
);
|
||||
`).toTransformInto(`
|
||||
(
|
||||
(base != null && base.a != null ? base.a.b : undefined) != null &&
|
||||
(base != null && base.a != null ? base.a.b : undefined).c != null ?
|
||||
(base != null && base.a != null ? base.a.b : undefined).c.d :
|
||||
undefined
|
||||
) != null
|
||||
&&
|
||||
(
|
||||
(base != null && base.a != null ? base.a.b : undefined) != null &&
|
||||
(base != null && base.a != null ? base.a.b : undefined).c != null ?
|
||||
(base != null && base.a != null ? base.a.b : undefined).c.d :
|
||||
undefined
|
||||
).e != null ?
|
||||
(
|
||||
(base != null && base.a != null ? base.a.b : undefined) != null &&
|
||||
(base != null && base.a != null ? base.a.b : undefined).c != null ?
|
||||
(base != null && base.a != null ? base.a.b : undefined).c.d :
|
||||
undefined
|
||||
).e.f :
|
||||
undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('transforms idx calls inside async functions (plugin order #1)', () => {
|
||||
expect({
|
||||
plugins: [babelPluginIdx, transformAsyncToGenerator],
|
||||
code: `
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
async function f() {
|
||||
idx(base, _ => _.b.c.d.e);
|
||||
}
|
||||
`,
|
||||
}).toTransformInto(`
|
||||
${asyncToGeneratorHelperCode}
|
||||
function f() {
|
||||
return _f.apply(this, arguments);
|
||||
}
|
||||
|
||||
function _f() {
|
||||
_f = _asyncToGenerator(function* () {
|
||||
base != null && base.b != null && base.b.c != null && base.b.c.d != null ? base.b.c.d.e : undefined;
|
||||
});
|
||||
return _f.apply(this, arguments);
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('transforms idx calls inside async functions (plugin order #2)', () => {
|
||||
expect({
|
||||
plugins: [transformAsyncToGenerator, babelPluginIdx],
|
||||
code: `
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
async function f() {
|
||||
idx(base, _ => _.b.c.d.e);
|
||||
}
|
||||
`,
|
||||
}).toTransformInto(`
|
||||
${asyncToGeneratorHelperCode}
|
||||
|
||||
function f() {
|
||||
return _f.apply(this, arguments);
|
||||
}
|
||||
|
||||
function _f() {
|
||||
_f = _asyncToGenerator(function* () {
|
||||
base != null && base.b != null && base.b.c != null && base.b.c.d != null ? base.b.c.d.e : undefined;
|
||||
});
|
||||
return _f.apply(this, arguments);
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('transforms idx calls in async methods', () => {
|
||||
expect({
|
||||
plugins: [transformAsyncToGenerator, babelPluginIdx],
|
||||
code: `
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
class Foo {
|
||||
async bar() {
|
||||
idx(base, _ => _.b);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
`,
|
||||
}).toTransformInto(`
|
||||
${asyncToGeneratorHelperCode}
|
||||
|
||||
class Foo {
|
||||
bar() {
|
||||
var _this = this;
|
||||
|
||||
return _asyncToGenerator(function* () {
|
||||
base != null ? base.b : undefined;
|
||||
return _this;
|
||||
})();
|
||||
}
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('transforms idx calls when an idx import binding is in scope', () => {
|
||||
expect(`
|
||||
import idx from '@kbn/elastic-idx';
|
||||
idx(base, _ => _.b);
|
||||
`).toTransformInto(`
|
||||
base != null ? base.b : undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('transforms idx calls when an idx const binding is in scope', () => {
|
||||
expect(`
|
||||
const idx = require('@kbn/elastic-idx');
|
||||
idx(base, _ => _.b);
|
||||
`).toTransformInto(`
|
||||
base != null ? base.b : undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('transforms deep idx calls when an idx import binding is in scope', () => {
|
||||
expect(`
|
||||
import idx from '@kbn/elastic-idx';
|
||||
function f() {
|
||||
idx(base, _ => _.b);
|
||||
}
|
||||
`).toTransformInto(`
|
||||
function f() {
|
||||
base != null ? base.b : undefined;
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('transforms deep idx calls when an idx const binding is in scope', () => {
|
||||
expect(`
|
||||
const idx = require('@kbn/elastic-idx');
|
||||
function f() {
|
||||
idx(base, _ => _.b);
|
||||
}
|
||||
`).toTransformInto(`
|
||||
function f() {
|
||||
base != null ? base.b : undefined;
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('transforms idx calls when an idx is called as a member function on the binding in scope', () => {
|
||||
expect(`
|
||||
const elastic_idx = require("@kbn/elastic-idx");
|
||||
const result = elastic_idx.idx(base, _ => _.a.b.c.d);
|
||||
`).toTransformInto(`
|
||||
const result = base != null &&
|
||||
base.a != null &&
|
||||
base.a.b != null &&
|
||||
base.a.b.c != null ?
|
||||
base.a.b.c.d :
|
||||
undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws on base call expressions', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, _ => _().b.c);
|
||||
`).toThrowTransformError('idx callbacks may only access properties on the callback parameter.');
|
||||
});
|
||||
|
||||
it('transforms when the idx parent is a scope creating expression', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
(() => idx(base, _ => _.b));
|
||||
`).toTransformInto(`
|
||||
() => base != null ? base.b : undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws if redefined before use', () => {
|
||||
expect(`
|
||||
let idx = require('@kbn/elastic-idx');
|
||||
idx = null;
|
||||
idx(base, _ => _.b);
|
||||
`).toThrowTransformError('`idx` cannot be redefined.');
|
||||
});
|
||||
|
||||
it('throws if redefined after use', () => {
|
||||
expect(`
|
||||
let idx = require('@kbn/elastic-idx');
|
||||
idx(base, _ => _.b);
|
||||
idx = null;
|
||||
`).toThrowTransformError('`idx` cannot be redefined.');
|
||||
});
|
||||
|
||||
it('throws if there is a duplicate declaration', () => {
|
||||
expect(() =>
|
||||
transform(`
|
||||
let idx = require('@kbn/elastic-idx');
|
||||
idx(base, _ => _.b);
|
||||
function idx() {}
|
||||
`)
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('handles sibling scopes with unique idx', () => {
|
||||
expect(`
|
||||
function aaa() {
|
||||
const idx = require('@kbn/elastic-idx');
|
||||
idx(base, _ => _.b);
|
||||
}
|
||||
function bbb() {
|
||||
const idx = require('@kbn/elastic-idx');
|
||||
idx(base, _ => _.b);
|
||||
}
|
||||
`).toTransformInto(`
|
||||
function aaa() {
|
||||
base != null ? base.b : undefined;
|
||||
}
|
||||
function bbb() {
|
||||
base != null ? base.b : undefined;
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('handles sibling scopes with and without idx', () => {
|
||||
expect(`
|
||||
function aaa() {
|
||||
const idx = require('@kbn/elastic-idx');
|
||||
idx(base, _ => _.b);
|
||||
}
|
||||
function bbb() {
|
||||
idx(base, _ => _.b);
|
||||
}
|
||||
`).toTransformInto(`
|
||||
function aaa() {
|
||||
base != null ? base.b : undefined;
|
||||
}
|
||||
function bbb() {
|
||||
idx(base, _ => _.b);
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('handles nested scopes with shadowing', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, _ => _.b);
|
||||
function aaa() {
|
||||
idx(base, _ => _.b);
|
||||
function bbb(idx) {
|
||||
idx(base, _ => _.b);
|
||||
}
|
||||
}
|
||||
`).toTransformInto(`
|
||||
base != null ? base.b : undefined;
|
||||
function aaa() {
|
||||
base != null ? base.b : undefined;
|
||||
function bbb(idx) {
|
||||
idx(base, _ => _.b);
|
||||
}
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('handles named idx import', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
idx(base, _ => _.b);
|
||||
`).toTransformInto(`
|
||||
base != null ? base.b : undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws on default plus named import', () => {
|
||||
expect(`
|
||||
import idx, {foo} from '@kbn/elastic-idx';
|
||||
idx(base, _ => _.b);
|
||||
`).toThrowTransformError('The idx import must be a single specifier.');
|
||||
});
|
||||
|
||||
it('throws on default plus namespace import', () => {
|
||||
expect(`
|
||||
import idx, * as foo from '@kbn/elastic-idx';
|
||||
idx(base, _ => _.b);
|
||||
`).toThrowTransformError('The idx import must be a single specifier.');
|
||||
});
|
||||
|
||||
it('throws on named default plus other import', () => {
|
||||
expect(`
|
||||
import {default as idx, foo} from '@kbn/elastic-idx';
|
||||
idx(base, _ => _.b);
|
||||
`).toThrowTransformError('The idx import must be a single specifier.');
|
||||
});
|
||||
|
||||
it('unused idx import should be left alone', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
`).toTransformInto(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
`);
|
||||
});
|
||||
|
||||
it('allows configuration of the import name', () => {
|
||||
expect({
|
||||
code: `
|
||||
import { idx } from 'i_d_x';
|
||||
idx(base, _ => _.b);
|
||||
`,
|
||||
options: { importName: 'i_d_x' },
|
||||
}).toTransformInto(`
|
||||
base != null ? base.b : undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('follows configuration of the import name', () => {
|
||||
expect({
|
||||
code: `
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { idx as i_d_x } from 'i_d_x';
|
||||
i_d_x(base, _ => _.b);
|
||||
idx(base, _ => _.c);
|
||||
`,
|
||||
options: { importName: 'i_d_x' },
|
||||
}).toTransformInto(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
|
||||
base != null ? base.b : undefined;
|
||||
idx(base, _ => _.c);
|
||||
`);
|
||||
});
|
||||
|
||||
it('allows configuration of the require name as a string', () => {
|
||||
expect({
|
||||
code: `
|
||||
import { idx } from 'i_d_x';
|
||||
idx(base, _ => _.b);
|
||||
`,
|
||||
options: { importName: 'i_d_x' },
|
||||
}).toTransformInto(`
|
||||
base != null ? base.b : undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('allows configuration of the require name as a RegExp', () => {
|
||||
expect({
|
||||
code: `
|
||||
import { idx } from '../../common/idx';
|
||||
idx(base, _ => _.b);
|
||||
`,
|
||||
options: { importName: /.*idx$/ },
|
||||
}).toTransformInto(`
|
||||
base != null ? base.b : undefined;
|
||||
`);
|
||||
});
|
||||
|
||||
it('follows configuration of the require name', () => {
|
||||
expect({
|
||||
code: `
|
||||
const idx = require('@kbn/elastic-idx');
|
||||
const i_d_x = require('i_d_x');
|
||||
i_d_x(base, _ => _.b);
|
||||
idx(base, _ => _.c);
|
||||
`,
|
||||
options: { importName: 'i_d_x' },
|
||||
}).toTransformInto(`
|
||||
const idx = require('@kbn/elastic-idx');
|
||||
|
||||
base != null ? base.b : undefined;
|
||||
idx(base, _ => _.c);
|
||||
`);
|
||||
});
|
||||
|
||||
describe('functional', () => {
|
||||
it('works with only properties', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
const base = {a: {b: {c: 2}}};
|
||||
idx(base, _ => _.a.b.c);
|
||||
`).toReturn(2);
|
||||
});
|
||||
|
||||
it('works with missing properties', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
const base = {a: {b: {}}};
|
||||
idx(base, _ => _.a.b.c);
|
||||
`).toReturn(undefined);
|
||||
});
|
||||
|
||||
it('works with null properties', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
const base = {a: {b: null}};
|
||||
idx(base, _ => _.a.b.c);
|
||||
`).toReturn(undefined);
|
||||
});
|
||||
|
||||
it('works with nested idx calls', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
const base = {a: {b: {c: {d: {e: {f: 2}}}}}};
|
||||
idx(
|
||||
idx(
|
||||
idx(base, _ => _.a.b),
|
||||
_ => _.c.d
|
||||
),
|
||||
_ => _.e.f
|
||||
);
|
||||
`).toReturn(2);
|
||||
});
|
||||
|
||||
it('works with nested idx calls with missing properties', () => {
|
||||
expect(`
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
const base = {a: {b: {c: null}}};
|
||||
idx(
|
||||
idx(
|
||||
idx(base, _ => _.a.b),
|
||||
_ => _.c.d
|
||||
),
|
||||
_ => _.e.f
|
||||
);
|
||||
`).toReturn(undefined);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"name": "@kbn/elastic-idx",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "Library for optional chaining & the Babel plugin to transpile idx calls to plain, optimized JS",
|
||||
"main": "target/index.js",
|
||||
"types": "target/index.d.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/elastic/kibana/tree/master/packages/kbn-elastic-idx"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"kbn:bootstrap": "yarn build",
|
||||
"kbn:watch": "yarn build --watch",
|
||||
"test": "jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.5.5",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.5.0",
|
||||
"jest": "^24.9.0",
|
||||
"typescript": "3.7.2"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
|
@ -1,65 +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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* DeepRequiredArray
|
||||
* Nested array condition handler
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface DeepRequiredArray<T> extends Array<DeepRequired<T>> {}
|
||||
|
||||
/**
|
||||
* DeepRequiredObject
|
||||
* Nested object condition handler
|
||||
*/
|
||||
type DeepRequiredObject<T> = { [P in keyof T]-?: DeepRequired<T[P]> };
|
||||
|
||||
/**
|
||||
* Function that has deeply required return type
|
||||
*/
|
||||
type FunctionWithRequiredReturnType<T extends (...args: any[]) => any> = T extends (
|
||||
...args: infer A
|
||||
) => infer R
|
||||
? (...args: A) => DeepRequired<R>
|
||||
: never;
|
||||
|
||||
/**
|
||||
* DeepRequired
|
||||
* Required that works for deeply nested structure
|
||||
*/
|
||||
type DeepRequired<T> = NonNullable<T> extends never
|
||||
? T
|
||||
: T extends any[]
|
||||
? DeepRequiredArray<T[number]>
|
||||
: T extends (...args: any[]) => any
|
||||
? FunctionWithRequiredReturnType<T>
|
||||
: NonNullable<T> extends object
|
||||
? DeepRequiredObject<NonNullable<T>>
|
||||
: T;
|
||||
|
||||
export function idx<T1, T2>(
|
||||
input: T1,
|
||||
accessor: (input: NonNullable<DeepRequired<T1>>) => T2
|
||||
): T2 | undefined {
|
||||
try {
|
||||
return accessor(input as NonNullable<DeepRequired<T1>>);
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"outDir": "./target"
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import {
|
||||
SavedObjectsClientContract,
|
||||
SimpleSavedObject,
|
||||
|
@ -64,7 +63,7 @@ export class IndexPatterns {
|
|||
if (!this.savedObjectsCache) {
|
||||
return [];
|
||||
}
|
||||
return this.savedObjectsCache.map(obj => idx(obj, _ => _.id));
|
||||
return this.savedObjectsCache.map(obj => obj?.id);
|
||||
};
|
||||
|
||||
getTitles = async (refresh: boolean = false): Promise<string[]> => {
|
||||
|
@ -74,7 +73,7 @@ export class IndexPatterns {
|
|||
if (!this.savedObjectsCache) {
|
||||
return [];
|
||||
}
|
||||
return this.savedObjectsCache.map(obj => idx(obj, _ => _.attributes.title));
|
||||
return this.savedObjectsCache.map(obj => obj?.attributes?.title);
|
||||
};
|
||||
|
||||
getFields = async (fields: string[], refresh: boolean = false) => {
|
||||
|
@ -86,7 +85,7 @@ export class IndexPatterns {
|
|||
}
|
||||
return this.savedObjectsCache.map((obj: Record<string, any>) => {
|
||||
const result: Record<string, any> = {};
|
||||
fields.forEach((f: string) => (result[f] = obj[f] || idx(obj, _ => _.attributes[f])));
|
||||
fields.forEach((f: string) => (result[f] = obj[f] || obj?.attributes?.[f]));
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
* crashing due to the requirement of the `window` object.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ReactNode, FunctionComponent } from 'react';
|
||||
|
@ -32,7 +31,7 @@ const layouts: Layouts = {
|
|||
};
|
||||
|
||||
export const findLayout = (type: InventoryItemType) => {
|
||||
const Layout = idx(layouts, _ => _[type]);
|
||||
const Layout = layouts?.[type];
|
||||
if (!Layout) {
|
||||
throw new Error(
|
||||
i18n.translate('xpack.infra.inventoryModels.findLayout.error', {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx/target';
|
||||
import { ReactNode, FunctionComponent } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { InventoryItemType } from './types';
|
||||
|
@ -24,7 +23,7 @@ const toolbars: Toolbars = {
|
|||
};
|
||||
|
||||
export const findToolbar = (type: InventoryItemType) => {
|
||||
const Toolbar = idx(toolbars, _ => _[type]);
|
||||
const Toolbar = toolbars?.[type];
|
||||
if (!Toolbar) {
|
||||
throw new Error(
|
||||
i18n.translate('xpack.infra.inventoryModels.findToolbar.error', {
|
||||
|
|
|
@ -8,7 +8,6 @@ import React, { useMemo, useState } from 'react';
|
|||
import { kfetch } from 'ui/kfetch';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { idx } from '@kbn/elastic-idx/target';
|
||||
import { KFetchError } from 'ui/kfetch/kfetch_error';
|
||||
import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { useTrackedPromise } from '../utils/use_tracked_promise';
|
||||
|
@ -44,13 +43,13 @@ export function useHTTPRequest<Response>(
|
|||
defaultMessage: `Error`,
|
||||
})}
|
||||
</h5>
|
||||
{idx(err.res, r => r.statusText)} ({idx(err.res, r => r.status)})
|
||||
{err.res?.statusText} ({err.res?.status})
|
||||
<h5>
|
||||
{i18n.translate('xpack.infra.useHTTPRequest.error.url', {
|
||||
defaultMessage: `URL`,
|
||||
})}
|
||||
</h5>
|
||||
{idx(err.res, r => r.url)}
|
||||
{err.res?.url}
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import { startsWith, uniq, first } from 'lodash';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { InfraDatabaseSearchResponse } from '../framework';
|
||||
import { KibanaFramework } from '../framework/kibana_framework_adapter';
|
||||
|
@ -105,8 +104,9 @@ export class FrameworkFieldsAdapter implements FieldsAdapter {
|
|||
|
||||
const bucketSelector = (response: InfraDatabaseSearchResponse<{}, DataSetResponse>) =>
|
||||
(response.aggregations && response.aggregations.datasets.buckets) || [];
|
||||
const handleAfterKey = createAfterKeyHandler('body.aggs.datasets.composite.after', input =>
|
||||
idx(input, _ => _.aggregations.datasets.after_key)
|
||||
const handleAfterKey = createAfterKeyHandler(
|
||||
'body.aggs.datasets.composite.after',
|
||||
input => input?.aggregations?.datasets?.after_key
|
||||
);
|
||||
|
||||
const buckets = await getAllCompositeData<DataSetResponse, Bucket>(
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import {
|
||||
InfraSnapshotGroupbyInput,
|
||||
|
@ -70,8 +69,9 @@ const bucketSelector = (
|
|||
response: InfraDatabaseSearchResponse<{}, InfraSnapshotAggregationResponse>
|
||||
) => (response.aggregations && response.aggregations.nodes.buckets) || [];
|
||||
|
||||
const handleAfterKey = createAfterKeyHandler('body.aggregations.nodes.composite.after', input =>
|
||||
idx(input, _ => _.aggregations.nodes.after_key)
|
||||
const handleAfterKey = createAfterKeyHandler(
|
||||
'body.aggregations.nodes.composite.after',
|
||||
input => input?.aggregations?.nodes?.after_key
|
||||
);
|
||||
|
||||
const requestGroupedNodes = async (
|
||||
|
|
|
@ -8,7 +8,6 @@ import { useEffect } from 'react';
|
|||
import { BehaviorSubject } from 'rxjs';
|
||||
import { filter, distinctUntilChanged } from 'rxjs/operators';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { ml } from '../../services/ml_api_service';
|
||||
import { Dictionary } from '../../../../common/types/common';
|
||||
|
@ -242,12 +241,13 @@ export const useRefreshAnalyticsList = (
|
|||
const DEFAULT_SIG_FIGS = 3;
|
||||
|
||||
export function getValuesFromResponse(response: RegressionEvaluateResponse) {
|
||||
let meanSquaredError = idx(response, _ => _.regression.mean_squared_error.error) as number;
|
||||
let meanSquaredError = response?.regression?.mean_squared_error?.error;
|
||||
|
||||
if (meanSquaredError) {
|
||||
meanSquaredError = Number(meanSquaredError.toPrecision(DEFAULT_SIG_FIGS));
|
||||
}
|
||||
|
||||
let rSquared = idx(response, _ => _.regression.r_squared.value) as number;
|
||||
let rSquared = response?.regression?.r_squared?.value;
|
||||
if (rSquared) {
|
||||
rSquared = Number(rSquared.toPrecision(DEFAULT_SIG_FIGS));
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import React, { FC, Fragment, useState, useEffect } from 'react';
|
||||
import moment from 'moment-timezone';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
|
||||
import { EuiIcon, EuiLoadingSpinner, EuiTabbedContent } from '@elastic/eui';
|
||||
|
||||
|
@ -64,7 +63,7 @@ export const ExpandedRow: FC<Props> = ({ item }) => {
|
|||
const [generalizationEval, setGeneralizationEval] = useState<Eval>(defaultEval);
|
||||
const [isLoadingTraining, setIsLoadingTraining] = useState<boolean>(false);
|
||||
const [isLoadingGeneralization, setIsLoadingGeneralization] = useState<boolean>(false);
|
||||
const index = idx(item, _ => _.config.dest.index) as string;
|
||||
const index = item?.config?.dest?.index;
|
||||
const dependentVariable = getDependentVar(item.config.analysis);
|
||||
const predictionFieldName = getPredictionFieldName(item.config.analysis);
|
||||
// default is 'ml'
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { validateIndexPattern } from 'ui/index_patterns';
|
||||
|
@ -43,7 +42,7 @@ export const mmlUnitInvalidErrorMessage = i18n.translate(
|
|||
const getSourceIndexString = (state: State) => {
|
||||
const { jobConfig } = state;
|
||||
|
||||
const sourceIndex = idx(jobConfig, _ => _.source.index);
|
||||
const sourceIndex = jobConfig?.source?.index;
|
||||
|
||||
if (typeof sourceIndex === 'string') {
|
||||
return sourceIndex;
|
||||
|
@ -70,17 +69,17 @@ export const validateAdvancedEditor = (state: State): State => {
|
|||
// `validateIndexPattern()` returns a map of messages, we're only interested here if it's valid or not.
|
||||
// If there are no messages, it means the index pattern is valid.
|
||||
let sourceIndexNameValid = Object.keys(validateIndexPattern(sourceIndexName)).length === 0;
|
||||
const sourceIndex = idx(jobConfig, _ => _.source.index);
|
||||
const sourceIndex = jobConfig?.source?.index;
|
||||
if (sourceIndexNameValid) {
|
||||
if (typeof sourceIndex === 'string') {
|
||||
sourceIndexNameValid = !sourceIndex.includes(',');
|
||||
}
|
||||
if (Array.isArray(sourceIndex)) {
|
||||
sourceIndexNameValid = !sourceIndex.some(d => d.includes(','));
|
||||
sourceIndexNameValid = !sourceIndex.some(d => d?.includes(','));
|
||||
}
|
||||
}
|
||||
|
||||
const destinationIndexName = idx(jobConfig, _ => _.dest.index) || '';
|
||||
const destinationIndexName = jobConfig?.dest?.index ?? '';
|
||||
const destinationIndexNameEmpty = destinationIndexName === '';
|
||||
const destinationIndexNameValid = isValidIndexName(destinationIndexName);
|
||||
const destinationIndexPatternTitleExists =
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
|
||||
import { getInitialState, getJobConfigFromFormState } from './state';
|
||||
|
||||
describe('useCreateAnalyticsForm', () => {
|
||||
|
@ -17,15 +15,15 @@ describe('useCreateAnalyticsForm', () => {
|
|||
|
||||
const jobConfig = getJobConfigFromFormState(state.form);
|
||||
|
||||
expect(idx(jobConfig, _ => _.dest.index)).toBe('the-destination-index');
|
||||
expect(idx(jobConfig, _ => _.source.index)).toBe('the-source-index');
|
||||
expect(idx(jobConfig, _ => _.analyzed_fields.excludes)).toStrictEqual([]);
|
||||
expect(typeof idx(jobConfig, _ => _.analyzed_fields.includes)).toBe('undefined');
|
||||
expect(jobConfig?.dest?.index).toBe('the-destination-index');
|
||||
expect(jobConfig?.source?.index).toBe('the-source-index');
|
||||
expect(jobConfig?.analyzed_fields?.excludes).toStrictEqual([]);
|
||||
expect(typeof jobConfig?.analyzed_fields?.includes).toBe('undefined');
|
||||
|
||||
// test the conversion of comma-separated Kibana index patterns to ES array based index patterns
|
||||
state.form.sourceIndex = 'the-source-index-1,the-source-index-2';
|
||||
const jobConfigSourceIndexArray = getJobConfigFromFormState(state.form);
|
||||
expect(idx(jobConfigSourceIndexArray, _ => _.source.index)).toStrictEqual([
|
||||
expect(jobConfigSourceIndexArray?.source?.index).toStrictEqual([
|
||||
'the-source-index-1',
|
||||
'the-source-index-2',
|
||||
]);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import { useReducer } from 'react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
|
||||
import { SimpleSavedObject } from 'src/core/public';
|
||||
import { ml } from '../../../../../services/ml_api_service';
|
||||
|
@ -229,9 +228,9 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
|
|||
const indexPatternsMap: SourceIndexMap = {};
|
||||
const savedObjects = (await kibanaContext.indexPatterns.getCache()) || [];
|
||||
savedObjects.forEach((obj: SimpleSavedObject<Record<string, any>>) => {
|
||||
const title = idx(obj, _ => _.attributes.title);
|
||||
const title = obj?.attributes?.title;
|
||||
if (title !== undefined) {
|
||||
const id = idx(obj, _ => _.id) || '';
|
||||
const id = obj?.id || '';
|
||||
indexPatternsMap[title] = { label: title, value: id };
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { Job, Datafeed, Detector } from '../configs';
|
||||
import { newJobCapsService } from '../../../../../services/new_job_capabilities_service';
|
||||
import {
|
||||
|
@ -120,10 +119,9 @@ function getDetectors(job: Job, datafeed: Datafeed) {
|
|||
) {
|
||||
// distinct count detector, field has been removed.
|
||||
// determine field from datafeed aggregations
|
||||
const field = idx<Datafeed, string>(
|
||||
datafeed,
|
||||
_ => _.aggregations.buckets.aggregations.dc_region.cardinality.field
|
||||
);
|
||||
const field = datafeed?.aggregations?.buckets?.aggregations?.dc_region?.cardinality
|
||||
?.field as string;
|
||||
|
||||
if (field !== undefined) {
|
||||
detectors = [
|
||||
{
|
||||
|
@ -193,10 +191,9 @@ function processFieldlessAggs(detectors: Detector[]) {
|
|||
export function isSparseDataJob(job: Job, datafeed: Datafeed): boolean {
|
||||
const detectors = job.analysis_config.detectors;
|
||||
|
||||
const distinctCountField = idx<Datafeed, string>(
|
||||
datafeed,
|
||||
_ => _.aggregations.buckets.aggregations.dc_region.cardinality.field
|
||||
);
|
||||
const distinctCountField = datafeed?.aggregations?.buckets?.aggregations?.dc_region?.cardinality
|
||||
?.field as string;
|
||||
|
||||
// if distinctCountField is undefined, and any detectors contain a sparse data function
|
||||
// return true
|
||||
if (distinctCountField === undefined) {
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
import { mlJobService } from '../../../../services/job_service';
|
||||
import { loadIndexPatterns, getIndexPatternIdFromName } from '../../../../util/index_utils';
|
||||
import { CombinedJob } from '../../common/job_creator/configs';
|
||||
|
@ -29,7 +28,7 @@ export async function preConfiguredJobRedirect() {
|
|||
}
|
||||
|
||||
function getWizardUrlFromCloningJob(job: CombinedJob) {
|
||||
const created = idx(job, _ => _.custom_settings.created_by);
|
||||
const created = job?.custom_settings?.created_by;
|
||||
let page = '';
|
||||
|
||||
if (created === CREATED_BY_LABEL.SINGLE_METRIC) {
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
|
||||
// This is similar to lodash's get() except that it's TypeScript aware and is able to infer return types.
|
||||
// It splits the attribute key string and uses reduce with an idx check to access nested attributes.
|
||||
export const getNestedProperty = (
|
||||
|
@ -13,5 +11,5 @@ export const getNestedProperty = (
|
|||
accessor: string,
|
||||
defaultValue?: any
|
||||
) => {
|
||||
return accessor.split('.').reduce((o, i) => idx(o, _ => _[i]), obj) || defaultValue;
|
||||
return accessor.split('.').reduce((o, i) => o?.[i], obj) || defaultValue;
|
||||
};
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
|
||||
// This is similar to lodash's get() except that it's TypeScript aware and is able to infer return types.
|
||||
// It splits the attribute key string and uses reduce with an idx check to access nested attributes.
|
||||
export const getNestedProperty = (
|
||||
|
@ -13,5 +11,5 @@ export const getNestedProperty = (
|
|||
accessor: string,
|
||||
defaultValue?: any
|
||||
) => {
|
||||
return accessor.split('.').reduce((o, i) => idx(o, _ => _[i]), obj) || defaultValue;
|
||||
return accessor.split('.').reduce((o, i) => o?.[i], obj) || defaultValue;
|
||||
};
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { idx } from '@kbn/elastic-idx';
|
||||
|
||||
import { TransformId } from './transform';
|
||||
import { TransformListRow } from './transform_list';
|
||||
|
||||
|
@ -78,8 +76,7 @@ export function getTransformProgress(item: TransformListRow) {
|
|||
return 100;
|
||||
}
|
||||
|
||||
const progress = idx(item, _ => _.stats.checkpointing.next.checkpoint_progress.percent_complete);
|
||||
|
||||
const progress = item?.stats?.checkpointing?.next?.checkpoint_progress?.percent_complete;
|
||||
return progress !== undefined ? Math.round(progress) : undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -183,7 +183,6 @@
|
|||
"@elastic/numeral": "2.3.3",
|
||||
"@kbn/babel-preset": "1.0.0",
|
||||
"@kbn/config-schema": "1.0.0",
|
||||
"@kbn/elastic-idx": "1.0.0",
|
||||
"@kbn/i18n": "1.0.0",
|
||||
"@kbn/interpreter": "1.0.0",
|
||||
"@kbn/ui-framework": "1.0.0",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue