Skip to content

Commit

Permalink
Merge branch 'main' into elasticsearch-integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Vadim Yakhin authored Mar 26, 2022
2 parents d75f08e + f522995 commit 584e9eb
Show file tree
Hide file tree
Showing 242 changed files with 7,157 additions and 2,969 deletions.
2 changes: 1 addition & 1 deletion docs/developer/plugin-list.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ security and spaces filtering.
|{kib-repo}blob/{branch}/x-pack/plugins/event_log/README.md[eventLog]
|The event log plugin provides a persistent history of alerting and action
actitivies.
activities.
|{kib-repo}blob/{branch}/x-pack/plugins/features/README.md[features]
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"@elastic/charts": "45.0.1",
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@8.2.0-canary.1",
"@elastic/ems-client": "8.1.0",
"@elastic/ems-client": "8.2.0",
"@elastic/eui": "51.1.0",
"@elastic/filesaver": "1.1.2",
"@elastic/node-crypto": "1.2.1",
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-es-query/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export {
nodeBuilder,
nodeTypes,
toElasticsearchQuery,
escapeKuery,
} from './kuery';

export {
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-es-query/src/kuery/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ export const toElasticsearchQuery = (...params: Parameters<typeof astToElasticse
export { KQLSyntaxError } from './kuery_syntax_error';
export { nodeTypes, nodeBuilder } from './node_types';
export { fromKueryExpression } from './ast';
export { escapeKuery } from './utils';
export type { FunctionTypeBuildNode, NodeTypes } from './node_types';
export type { DslQuery, KueryNode, KueryQueryOptions, KueryParseOptions } from './types';
60 changes: 60 additions & 0 deletions packages/kbn-es-query/src/kuery/utils/escape_kuery.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { escapeKuery } from './escape_kuery';

describe('escapeKuery', () => {
test('should escape special characters', () => {
const value = `This \\ has (a lot of) <special> characters, don't you *think*? "Yes."`;
const expected = `This \\\\ has \\(a lot of\\) \\<special\\> characters, don't you \\*think\\*? \\"Yes.\\"`;

expect(escapeKuery(value)).toBe(expected);
});

test('should escape keywords', () => {
const value = 'foo and bar or baz not qux';
const expected = 'foo \\and bar \\or baz \\not qux';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape keywords next to each other', () => {
const value = 'foo and bar or not baz';
const expected = 'foo \\and bar \\or \\not baz';

expect(escapeKuery(value)).toBe(expected);
});

test('should not escape keywords without surrounding spaces', () => {
const value = 'And this has keywords, or does it not?';
const expected = 'And this has keywords, \\or does it not?';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape uppercase keywords', () => {
const value = 'foo AND bar';
const expected = 'foo \\AND bar';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape both keywords and special characters', () => {
const value = 'Hello, world, and <nice> to meet you!';
const expected = 'Hello, world, \\and \\<nice\\> to meet you!';

expect(escapeKuery(value)).toBe(expected);
});

test('should escape newlines and tabs', () => {
const value = 'This\nhas\tnewlines\r\nwith\ttabs';
const expected = 'This\\nhas\\tnewlines\\r\\nwith\\ttabs';

expect(escapeKuery(value)).toBe(expected);
});
});
34 changes: 34 additions & 0 deletions packages/kbn-es-query/src/kuery/utils/escape_kuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { flow } from 'lodash';

/**
* Escapes a Kuery node value to ensure that special characters, operators, and whitespace do not result in a parsing error or unintended
* behavior when using the value as an argument for the `buildNode` function.
*/
export const escapeKuery = flow(escapeSpecialCharacters, escapeAndOr, escapeNot, escapeWhitespace);

// See the SpecialCharacter rule in kuery.peg
function escapeSpecialCharacters(str: string) {
return str.replace(/[\\():<>"*]/g, '\\$&'); // $& means the whole matched string
}

// See the Keyword rule in kuery.peg
function escapeAndOr(str: string) {
return str.replace(/(\s+)(and|or)(\s+)/gi, '$1\\$2$3');
}

function escapeNot(str: string) {
return str.replace(/not(\s+)/gi, '\\$&');
}

// See the Space rule in kuery.peg
function escapeWhitespace(str: string) {
return str.replace(/\t/g, '\\t').replace(/\r/g, '\\r').replace(/\n/g, '\\n');
}
9 changes: 9 additions & 0 deletions packages/kbn-es-query/src/kuery/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { escapeKuery } from './escape_kuery';
17 changes: 17 additions & 0 deletions packages/kbn-shared-ux-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,20 @@ export const LazyIconButtonGroup = React.lazy(() =>
* The IconButtonGroup component that is wrapped by the `withSuspence` HOC.
*/
export const IconButtonGroup = withSuspense(LazyIconButtonGroup);

/**
* The Lazily-loaded `KibanaSolutionAvatar` component. Consumers should use `React.Suspense` or
* the withSuspense` HOC to load this component.
*/
export const KibanaSolutionAvatarLazy = React.lazy(() =>
import('./solution_avatar').then(({ KibanaSolutionAvatar }) => ({
default: KibanaSolutionAvatar,
}))
);

/**
* A `KibanaSolutionAvatar` component that is wrapped by the `withSuspense` HOC. This component can
* be used directly by consumers and will load the `KibanaPageTemplateSolutionNavAvatarLazy` component lazily with
* a predefined fallback and error boundary.
*/
export const KibanaSolutionAvatar = withSuspense(KibanaSolutionAvatarLazy);

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { KibanaSolutionAvatar } from './solution_avatar';
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.kbnSolutionAvatar {
@include euiBottomShadowSmall;

&--xxl {
@include euiBottomShadowMedium;
@include size(100px);
line-height: 100px;
border-radius: 100px;
display: inline-block;
background: $euiColorEmptyShade url('/assets/texture.svg') no-repeat;
background-size: cover, 125%;
text-align: center;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { KibanaSolutionAvatar, KibanaSolutionAvatarProps } from './solution_avatar';

export default {
title: 'Solution Avatar',
description: 'A wrapper around EuiAvatar, specifically to stylize Elastic Solutions',
};

type Params = Pick<KibanaSolutionAvatarProps, 'size' | 'name'>;

export const PureComponent = (params: Params) => {
return <KibanaSolutionAvatar {...params} />;
};

PureComponent.argTypes = {
name: {
control: 'text',
defaultValue: 'Kibana',
},
size: {
control: 'radio',
options: ['s', 'm', 'l', 'xl', 'xxl'],
defaultValue: 'xxl',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { shallow } from 'enzyme';
import { KibanaSolutionAvatar } from './solution_avatar';

describe('KibanaSolutionAvatar', () => {
test('renders', () => {
const component = shallow(<KibanaSolutionAvatar name="Solution" iconType="logoElastic" />);
expect(component).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import './solution_avatar.scss';

import React from 'react';
import classNames from 'classnames';

import { DistributiveOmit, EuiAvatar, EuiAvatarProps } from '@elastic/eui';

export type KibanaSolutionAvatarProps = DistributiveOmit<EuiAvatarProps, 'size'> & {
/**
* Any EuiAvatar size available, or `xxl` for custom large, brand-focused version
*/
size?: EuiAvatarProps['size'] | 'xxl';
};

/**
* Applies extra styling to a typical EuiAvatar;
* The `name` value will be appended to 'logo' to configure the `iconType` unless `iconType` is provided.
*/
export const KibanaSolutionAvatar = ({ className, size, ...rest }: KibanaSolutionAvatarProps) => {
return (
// @ts-ignore Complains about ExclusiveUnion between `iconSize` and `iconType`, but works fine
<EuiAvatar
className={classNames(
'kbnSolutionAvatar',
{
[`kbnSolutionAvatar--${size}`]: size,
},
className
)}
color="plain"
size={size === 'xxl' ? 'xl' : size}
iconSize={size}
iconType={`logo${rest.name}`}
{...rest}
/>
);
};
2 changes: 1 addition & 1 deletion packages/kbn-test/jest-preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = {
coverageDirectory: '<rootDir>/target/kibana-coverage/jest',

// An array of regexp pattern strings used to skip coverage collection
coveragePathIgnorePatterns: ['/node_modules/', '.*\\.d\\.ts'],
coveragePathIgnorePatterns: ['/node_modules/', '.*\\.d\\.ts', 'jest\\.config\\.js'],

// A list of reporter names that Jest uses when writing coverage reports
coverageReporters: !!process.env.CODE_COVERAGE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ jest.mock('../../../../elasticsearch', () => {
return { getErrorMessage: mockGetEsErrorMessage };
});

// Mock these functions to return empty results, as this simplifies test cases and we don't need to exercise alternate code paths for these
jest.mock('@kbn/es-query', () => {
return { nodeTypes: { function: { buildNode: jest.fn() } } };
});
// Mock this function to return empty results, as this simplifies test cases and we don't need to exercise alternate code paths for these
jest.mock('../search_dsl', () => {
return { getSearchDsl: jest.fn() };
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ describe('deleteLegacyUrlAliases', () => {
};
}

const type = 'obj-type';
const id = 'obj-id';
// Include KQL special characters in the object type/ID to implicitly assert that the kuery node builder handles it gracefully
const type = 'obj-type:"';
const id = 'id-1:"';

it('throws an error if namespaces includes the "all namespaces" string', async () => {
const namespaces = [ALL_NAMESPACES_STRING];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,6 @@ export async function deleteLegacyUrlAliases(params: DeleteLegacyUrlAliasesParam
return;
}

const { buildNode } = esKuery.nodeTypes.function;
const match1 = buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.targetType`, type);
const match2 = buildNode('is', `${LEGACY_URL_ALIAS_TYPE}.targetId`, id);
const kueryNode = buildNode('and', [match1, match2]);

try {
await client.updateByQuery(
{
Expand All @@ -75,7 +70,7 @@ export async function deleteLegacyUrlAliases(params: DeleteLegacyUrlAliasesParam
body: {
...getSearchDsl(mappings, registry, {
type: LEGACY_URL_ALIAS_TYPE,
kueryNode,
kueryNode: createKueryNode(type, id),
}),
script: {
// Intentionally use one script source with variable params to take advantage of ES script caching
Expand Down Expand Up @@ -107,3 +102,17 @@ export async function deleteLegacyUrlAliases(params: DeleteLegacyUrlAliasesParam
function throwError(type: string, id: string, message: string) {
throw new Error(`Failed to delete legacy URL aliases for ${type}/${id}: ${message}`);
}

function getKueryKey(attribute: string) {
// Note: these node keys do NOT include '.attributes' for type-level fields because we are using the query in the ES client (instead of the SO client)
return `${LEGACY_URL_ALIAS_TYPE}.${attribute}`;
}

export function createKueryNode(type: string, id: string) {
const { buildNode } = esKuery.nodeTypes.function;
// Escape Kuery values to prevent parsing errors and unintended behavior (object types/IDs can contain KQL special characters/operators)
const match1 = buildNode('is', getKueryKey('targetType'), esKuery.escapeKuery(type));
const match2 = buildNode('is', getKueryKey('targetId'), esKuery.escapeKuery(id));
const kueryNode = buildNode('and', [match1, match2]);
return kueryNode;
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ describe('findLegacyUrlAliases', () => {
});
}

const obj1 = { type: 'obj-type', id: 'id-1' };
// Include KQL special characters in the object type/ID to implicitly assert that the kuery node builder handles it gracefully
const obj1 = { type: 'obj-type:"', id: 'id-1:"' };
const obj2 = { type: 'obj-type', id: 'id-2' };
const obj3 = { type: 'obj-type', id: 'id-3' };

Expand Down
Loading

0 comments on commit 584e9eb

Please sign in to comment.