diff --git a/docs/api/saved-objects/bulk_create.asciidoc b/docs/api/saved-objects/bulk_create.asciidoc index 5149cef3d30c6..267ab3891d700 100644 --- a/docs/api/saved-objects/bulk_create.asciidoc +++ b/docs/api/saved-objects/bulk_create.asciidoc @@ -41,9 +41,10 @@ experimental[] Create multiple {kib} saved objects. `references`:: (Optional, array) Objects with `name`, `id`, and `type` properties that describe the other saved objects in the referenced object. To refer to the other saved object, use `name` in the attributes. Never use `id` to refer to the other saved object. `id` can be automatically updated during migrations, import, or export. -`namespaces`:: - (Optional, string array) Identifiers for the <> in which this object should be created. If this is not provided, the - object will be created in the current space. +`initialNamespaces`:: + (Optional, string array) Identifiers for the <> in which this object is created. If this is provided, the + object is created only in the explicitly defined spaces. If this is not provided, the object is created in the current space + (default behavior). `version`:: (Optional, number) Specifies the version. diff --git a/docs/api/saved-objects/create.asciidoc b/docs/api/saved-objects/create.asciidoc index c8cd9c8bfca27..50809a1bd5d4e 100644 --- a/docs/api/saved-objects/create.asciidoc +++ b/docs/api/saved-objects/create.asciidoc @@ -46,9 +46,10 @@ any data that you send to the API is properly formed. `references`:: (Optional, array) Objects with `name`, `id`, and `type` properties that describe the other saved objects that this object references. Use `name` in attributes to refer to the other saved object, but never the `id`, which can update automatically during migrations or import/export. -`namespaces`:: - (Optional, string array) Identifiers for the <> in which this object should be created. If this is not provided, the - object will be created in the current space. +`initialNamespaces`:: + (Optional, string array) Identifiers for the <> in which this object is created. If this is provided, the + object is created only in the explicitly defined spaces. If this is not provided, the object is created in the current space + (default behavior). [[saved-objects-api-create-request-codes]] ==== Response code diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.namespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md similarity index 68% rename from docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.namespaces.md rename to docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md index 7db1c53c67b52..3db8bbadfbd6b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.namespaces.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsBulkCreateObject](./kibana-plugin-core-server.savedobjectsbulkcreateobject.md) > [namespaces](./kibana-plugin-core-server.savedobjectsbulkcreateobject.namespaces.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsBulkCreateObject](./kibana-plugin-core-server.savedobjectsbulkcreateobject.md) > [initialNamespaces](./kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md) -## SavedObjectsBulkCreateObject.namespaces property +## SavedObjectsBulkCreateObject.initialNamespaces property Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md). @@ -11,5 +11,5 @@ Note: this can only be used for multi-namespace object types. Signature: ```typescript -namespaces?: string[]; +initialNamespaces?: string[]; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md index aabbfeeff75af..5ac5f6d9807bd 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsbulkcreateobject.md @@ -17,8 +17,8 @@ export interface SavedObjectsBulkCreateObject | --- | --- | --- | | [attributes](./kibana-plugin-core-server.savedobjectsbulkcreateobject.attributes.md) | T | | | [id](./kibana-plugin-core-server.savedobjectsbulkcreateobject.id.md) | string | | +| [initialNamespaces](./kibana-plugin-core-server.savedobjectsbulkcreateobject.initialnamespaces.md) | string[] | Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).Note: this can only be used for multi-namespace object types. | | [migrationVersion](./kibana-plugin-core-server.savedobjectsbulkcreateobject.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | -| [namespaces](./kibana-plugin-core-server.savedobjectsbulkcreateobject.namespaces.md) | string[] | Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).Note: this can only be used for multi-namespace object types. | | [originId](./kibana-plugin-core-server.savedobjectsbulkcreateobject.originid.md) | string | Optional ID of the original saved object, if this object's id was regenerated | | [references](./kibana-plugin-core-server.savedobjectsbulkcreateobject.references.md) | SavedObjectReference[] | | | [type](./kibana-plugin-core-server.savedobjectsbulkcreateobject.type.md) | string | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.namespaces.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md similarity index 69% rename from docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.namespaces.md rename to docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md index 67804999dfd44..262b0997cb905 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.namespaces.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md) > [namespaces](./kibana-plugin-core-server.savedobjectscreateoptions.namespaces.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md) > [initialNamespaces](./kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md) -## SavedObjectsCreateOptions.namespaces property +## SavedObjectsCreateOptions.initialNamespaces property Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md). @@ -11,5 +11,5 @@ Note: this can only be used for multi-namespace object types. Signature: ```typescript -namespaces?: string[]; +initialNamespaces?: string[]; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md index 63aebf6c5e791..e6d306784f8ae 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectscreateoptions.md @@ -16,8 +16,8 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions | Property | Type | Description | | --- | --- | --- | | [id](./kibana-plugin-core-server.savedobjectscreateoptions.id.md) | string | (not recommended) Specify an id for the document | +| [initialNamespaces](./kibana-plugin-core-server.savedobjectscreateoptions.initialnamespaces.md) | string[] | Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).Note: this can only be used for multi-namespace object types. | | [migrationVersion](./kibana-plugin-core-server.savedobjectscreateoptions.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | -| [namespaces](./kibana-plugin-core-server.savedobjectscreateoptions.namespaces.md) | string[] | Optional initial namespaces for the object to be created in. If this is defined, it will supersede the namespace ID that is in [SavedObjectsCreateOptions](./kibana-plugin-core-server.savedobjectscreateoptions.md).Note: this can only be used for multi-namespace object types. | | [originId](./kibana-plugin-core-server.savedobjectscreateoptions.originid.md) | string | Optional ID of the original saved object, if this object's id was regenerated | | [overwrite](./kibana-plugin-core-server.savedobjectscreateoptions.overwrite.md) | boolean | Overwrite existing documents (defaults to false) | | [references](./kibana-plugin-core-server.savedobjectscreateoptions.references.md) | SavedObjectReference[] | | diff --git a/src/core/server/saved_objects/routes/bulk_create.ts b/src/core/server/saved_objects/routes/bulk_create.ts index 0f925d61ead98..b048c5d8f99bf 100644 --- a/src/core/server/saved_objects/routes/bulk_create.ts +++ b/src/core/server/saved_objects/routes/bulk_create.ts @@ -44,7 +44,7 @@ export const registerBulkCreateRoute = (router: IRouter) => { }) ) ), - namespaces: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })), + initialNamespaces: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })), }) ), }, diff --git a/src/core/server/saved_objects/routes/create.ts b/src/core/server/saved_objects/routes/create.ts index 191dbfaa0dbf1..816315705a375 100644 --- a/src/core/server/saved_objects/routes/create.ts +++ b/src/core/server/saved_objects/routes/create.ts @@ -44,16 +44,16 @@ export const registerCreateRoute = (router: IRouter) => { }) ) ), - namespaces: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })), + initialNamespaces: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })), }), }, }, router.handleLegacyErrors(async (context, req, res) => { const { type, id } = req.params; const { overwrite } = req.query; - const { attributes, migrationVersion, references, namespaces } = req.body; + const { attributes, migrationVersion, references, initialNamespaces } = req.body; - const options = { id, overwrite, migrationVersion, references, namespaces }; + const options = { id, overwrite, migrationVersion, references, initialNamespaces }; const result = await context.core.savedObjects.client.create(type, attributes, options); return res.ok({ body: result }); }) diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 10c7f143e52b9..e93bdb34ecc75 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -635,13 +635,13 @@ describe('SavedObjectsRepository', () => { await test(namespace); }); - it(`adds namespaces instead of namespace`, async () => { + it(`adds initialNamespaces instead of namespace`, async () => { const test = async (namespace) => { const ns2 = 'bar-namespace'; const ns3 = 'baz-namespace'; const objects = [ - { ...obj1, type: MULTI_NAMESPACE_TYPE, namespaces: [ns2] }, - { ...obj2, type: MULTI_NAMESPACE_TYPE, namespaces: [ns3] }, + { ...obj1, type: MULTI_NAMESPACE_TYPE, initialNamespaces: [ns2] }, + { ...obj2, type: MULTI_NAMESPACE_TYPE, initialNamespaces: [ns3] }, ]; await bulkCreateSuccess(objects, { namespace, overwrite: true }); const body = [ @@ -758,15 +758,15 @@ describe('SavedObjectsRepository', () => { ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); }); - it(`returns error when namespaces is used with a non-multi-namespace object`, async () => { + it(`returns error when initialNamespaces is used with a non-multi-namespace object`, async () => { const test = async (objType) => { - const obj = { ...obj3, type: objType, namespaces: [] }; + const obj = { ...obj3, type: objType, initialNamespaces: [] }; await bulkCreateError( obj, undefined, expectErrorResult( obj, - createBadRequestError('"namespaces" can only be used on multi-namespace types') + createBadRequestError('"initialNamespaces" can only be used on multi-namespace types') ) ); }; @@ -774,14 +774,14 @@ describe('SavedObjectsRepository', () => { await test(NAMESPACE_AGNOSTIC_TYPE); }); - it(`throws when options.namespaces is used with a multi-namespace type and is empty`, async () => { - const obj = { ...obj3, type: MULTI_NAMESPACE_TYPE, namespaces: [] }; + it(`throws when options.initialNamespaces is used with a multi-namespace type and is empty`, async () => { + const obj = { ...obj3, type: MULTI_NAMESPACE_TYPE, initialNamespaces: [] }; await bulkCreateError( obj, undefined, expectErrorResult( obj, - createBadRequestError('"namespaces" must be a non-empty array of strings') + createBadRequestError('"initialNamespaces" must be a non-empty array of strings') ) ); }); @@ -1993,13 +1993,13 @@ describe('SavedObjectsRepository', () => { ); }); - it(`adds namespaces instead of namespace`, async () => { - const options = { id, namespace, namespaces: ['bar-namespace', 'baz-namespace'] }; + it(`adds initialNamespaces instead of namespace`, async () => { + const options = { id, namespace, initialNamespaces: ['bar-namespace', 'baz-namespace'] }; await createSuccess(MULTI_NAMESPACE_TYPE, attributes, options); expect(client.create).toHaveBeenCalledWith( expect.objectContaining({ id: `${MULTI_NAMESPACE_TYPE}:${id}`, - body: expect.objectContaining({ namespaces: options.namespaces }), + body: expect.objectContaining({ namespaces: options.initialNamespaces }), }), expect.anything() ); @@ -2021,23 +2021,25 @@ describe('SavedObjectsRepository', () => { }); describe('errors', () => { - it(`throws when options.namespaces is used with a non-multi-namespace object`, async () => { + it(`throws when options.initialNamespaces is used with a non-multi-namespace object`, async () => { const test = async (objType) => { await expect( - savedObjectsRepository.create(objType, attributes, { namespaces: [namespace] }) + savedObjectsRepository.create(objType, attributes, { initialNamespaces: [namespace] }) ).rejects.toThrowError( - createBadRequestError('"options.namespaces" can only be used on multi-namespace types') + createBadRequestError( + '"options.initialNamespaces" can only be used on multi-namespace types' + ) ); }; await test('dashboard'); await test(NAMESPACE_AGNOSTIC_TYPE); }); - it(`throws when options.namespaces is used with a multi-namespace type and is empty`, async () => { + it(`throws when options.initialNamespaces is used with a multi-namespace type and is empty`, async () => { await expect( - savedObjectsRepository.create(MULTI_NAMESPACE_TYPE, attributes, { namespaces: [] }) + savedObjectsRepository.create(MULTI_NAMESPACE_TYPE, attributes, { initialNamespaces: [] }) ).rejects.toThrowError( - createBadRequestError('"options.namespaces" must be a non-empty array of strings') + createBadRequestError('"options.initialNamespaces" must be a non-empty array of strings') ); }); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index bae96ceec2783..39aacd6b05b7b 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -230,19 +230,19 @@ export class SavedObjectsRepository { references = [], refresh = DEFAULT_REFRESH_SETTING, originId, - namespaces, + initialNamespaces, version, } = options; const namespace = normalizeNamespace(options.namespace); - if (namespaces) { + if (initialNamespaces) { if (!this._registry.isMultiNamespace(type)) { throw SavedObjectsErrorHelpers.createBadRequestError( - '"options.namespaces" can only be used on multi-namespace types' + '"options.initialNamespaces" can only be used on multi-namespace types' ); - } else if (!namespaces.length) { + } else if (!initialNamespaces.length) { throw SavedObjectsErrorHelpers.createBadRequestError( - '"options.namespaces" must be a non-empty array of strings' + '"options.initialNamespaces" must be a non-empty array of strings' ); } } @@ -262,9 +262,9 @@ export class SavedObjectsRepository { // we will overwrite a multi-namespace saved object if it exists; if that happens, ensure we preserve its included namespaces // note: this check throws an error if the object is found but does not exist in this namespace const existingNamespaces = await this.preflightGetNamespaces(type, id, namespace); - savedObjectNamespaces = namespaces || existingNamespaces; + savedObjectNamespaces = initialNamespaces || existingNamespaces; } else { - savedObjectNamespaces = namespaces || getSavedObjectNamespaces(namespace); + savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces(namespace); } } @@ -323,14 +323,14 @@ export class SavedObjectsRepository { let error: DecoratedError | undefined; if (!this._allowedTypes.includes(object.type)) { error = SavedObjectsErrorHelpers.createUnsupportedTypeError(object.type); - } else if (object.namespaces) { + } else if (object.initialNamespaces) { if (!this._registry.isMultiNamespace(object.type)) { error = SavedObjectsErrorHelpers.createBadRequestError( - '"namespaces" can only be used on multi-namespace types' + '"initialNamespaces" can only be used on multi-namespace types' ); - } else if (!object.namespaces.length) { + } else if (!object.initialNamespaces.length) { error = SavedObjectsErrorHelpers.createBadRequestError( - '"namespaces" must be a non-empty array of strings' + '"initialNamespaces" must be a non-empty array of strings' ); } } @@ -388,7 +388,7 @@ export class SavedObjectsRepository { let versionProperties; const { esRequestIndex, - object: { namespaces, version, ...object }, + object: { initialNamespaces, version, ...object }, method, } = expectedBulkGetResult.value; if (esRequestIndex !== undefined) { @@ -410,13 +410,13 @@ export class SavedObjectsRepository { }; } savedObjectNamespaces = - namespaces || getSavedObjectNamespaces(namespace, docFound && actualResult); + initialNamespaces || getSavedObjectNamespaces(namespace, docFound && actualResult); versionProperties = getExpectedVersionProperties(version, actualResult); } else { if (this._registry.isSingleNamespace(object.type)) { savedObjectNamespace = namespace; } else if (this._registry.isMultiNamespace(object.type)) { - savedObjectNamespaces = namespaces || getSavedObjectNamespaces(namespace); + savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces(namespace); } versionProperties = getExpectedVersionProperties(version); } diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index d2b3b89b928c7..6782998d1bf1e 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -56,7 +56,7 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { * * Note: this can only be used for multi-namespace object types. */ - namespaces?: string[]; + initialNamespaces?: string[]; } /** @@ -79,7 +79,7 @@ export interface SavedObjectsBulkCreateObject { * * Note: this can only be used for multi-namespace object types. */ - namespaces?: string[]; + initialNamespaces?: string[]; } /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index a877700a48bcb..20bd102e6f507 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1936,8 +1936,8 @@ export interface SavedObjectsBulkCreateObject { attributes: T; // (undocumented) id?: string; + initialNamespaces?: string[]; migrationVersion?: SavedObjectsMigrationVersion; - namespaces?: string[]; originId?: string; // (undocumented) references?: SavedObjectReference[]; @@ -2094,8 +2094,8 @@ export interface SavedObjectsCoreFieldMapping { // @public (undocumented) export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { id?: string; + initialNamespaces?: string[]; migrationVersion?: SavedObjectsMigrationVersion; - namespaces?: string[]; originId?: string; overwrite?: boolean; // (undocumented) diff --git a/src/dev/typescript/build_refs.ts b/src/dev/typescript/build_refs.ts index 2cc8283111959..fdc1dfbfffa0b 100644 --- a/src/dev/typescript/build_refs.ts +++ b/src/dev/typescript/build_refs.ts @@ -29,7 +29,7 @@ export async function buildAllRefs(log: ToolingLog) { async function buildRefs(log: ToolingLog, projectPath: string) { try { log.debug(`Building TypeScript projects refs for ${projectPath}...`); - await execa(require.resolve('typescript/bin/tsc'), ['-b', projectPath]); + await execa(require.resolve('typescript/bin/tsc'), ['-b', projectPath, '--pretty']); } catch (e) { log.error(e); process.exit(1); diff --git a/src/plugins/legacy_export/server/routes/import.ts b/src/plugins/legacy_export/server/routes/import.ts index 8d33983ad7e3c..135d79d782fd7 100644 --- a/src/plugins/legacy_export/server/routes/import.ts +++ b/src/plugins/legacy_export/server/routes/import.ts @@ -28,7 +28,7 @@ export const registerImportRoute = (router: IRouter, maxImportPayloadBytes: numb validate: { body: schema.object({ objects: schema.arrayOf(schema.recordOf(schema.string(), schema.any())), - version: schema.string(), + version: schema.maybe(schema.string()), }), query: schema.object({ force: schema.boolean({ defaultValue: false }), diff --git a/x-pack/plugins/canvas/public/components/datasource/__stories__/__snapshots__/datasource_component.stories.storyshot b/x-pack/plugins/canvas/public/components/datasource/__stories__/__snapshots__/datasource_component.stories.storyshot new file mode 100644 index 0000000000000..373c147c2a5b8 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/datasource/__stories__/__snapshots__/datasource_component.stories.storyshot @@ -0,0 +1,129 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/datasource/DatasourceComponent datasource with expression arguments 1`] = ` +
+ +
+
+
+

+ The datasource has an argument controlled by an expression. Use the expression editor to modify the datasource. +

+
+
+
+`; + +exports[`Storyshots components/datasource/DatasourceComponent simple datasource 1`] = ` +
+ +
+
+
+
+
+ +
+
+ +
+
+
+`; diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__stories__/__snapshots__/simple_template.stories.storyshot b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__stories__/__snapshots__/simple_template.stories.storyshot index 495bf5262476c..9eeffde84ffac 100644 --- a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__stories__/__snapshots__/simple_template.stories.storyshot +++ b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__stories__/__snapshots__/simple_template.stories.storyshot @@ -28,7 +28,6 @@ exports[`Storyshots arguments/ContainerStyle simple 1`] = ` >
({ + getDefaultIndex: () => Promise.resolve('test index'), +})); + addSerializer(styleSheetSerializer); // Initialize Storyshots and build the Jest Snapshots -// Commenting this out until after #75357 is merged and Jest gets updated. -// initStoryshots({ -// configPath: path.resolve(__dirname, './../storybook'), -// test: multiSnapshotWithOptions({}), -// // Don't snapshot tests that start with 'redux' -// storyNameRegex: /^((?!.*?redux).)*$/, -// }); - -test.todo('Storyshots'); +initStoryshots({ + configPath: path.resolve(__dirname, './../storybook'), + test: multiSnapshotWithOptions({}), + // Don't snapshot tests that start with 'redux' + storyNameRegex: /^((?!.*?redux).)*$/, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 3c7979ed3d4b2..ab5b3c9faeea7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { Redirect } from 'react-router-dom'; import { shallow } from 'enzyme'; -import { SideNav, SideNavLink } from '../shared/layout'; +import { Layout, SideNav, SideNavLink } from '../shared/layout'; import { SetupGuide } from './components/setup_guide'; import { ErrorConnecting } from './components/error_connecting'; import { EngineOverview } from './components/engine_overview'; @@ -51,9 +51,11 @@ describe('AppSearchConfigured', () => { setMockActions({ initializeAppData: () => {} }); }); - it('renders', () => { + it('renders with layout', () => { const wrapper = shallow(); + expect(wrapper.find(Layout)).toHaveLength(1); + expect(wrapper.find(Layout).prop('readOnlyMode')).toBeFalsy(); expect(wrapper.find(EngineOverview)).toHaveLength(1); }); @@ -84,6 +86,14 @@ describe('AppSearchConfigured', () => { expect(wrapper.find(ErrorConnecting)).toHaveLength(1); }); + it('passes readOnlyMode state', () => { + setMockValues({ myRole: {}, readOnlyMode: true }); + + const wrapper = shallow(); + + expect(wrapper.find(Layout).prop('readOnlyMode')).toEqual(true); + }); + describe('ability checks', () => { // TODO: Use this section for routes wrapped in canViewX conditionals // e.g., it('renders settings if a user can view settings') diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index 49e0a8a484de1..9aa2cce9c74df 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -8,7 +8,6 @@ import React, { useEffect } from 'react'; import { Route, Redirect, Switch } from 'react-router-dom'; import { useActions, useValues } from 'kea'; -import { EuiPage, EuiPageBody } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { getAppSearchUrl } from '../shared/enterprise_search_url'; @@ -18,7 +17,7 @@ import { AppLogic } from './app_logic'; import { IInitialAppData } from '../../../common/types'; import { APP_SEARCH_PLUGIN } from '../../../common/constants'; -import { SideNav, SideNavLink } from '../shared/layout'; +import { Layout, SideNav, SideNavLink } from '../shared/layout'; import { ROOT_PATH, @@ -53,7 +52,7 @@ export const AppSearchUnconfigured: React.FC = () => ( export const AppSearchConfigured: React.FC = (props) => { const { initializeAppData } = useActions(AppLogic); const { hasInitialized } = useValues(AppLogic); - const { errorConnecting } = useValues(HttpLogic); + const { errorConnecting, readOnlyMode } = useValues(HttpLogic); useEffect(() => { if (!hasInitialized) initializeAppData(props); @@ -65,25 +64,23 @@ export const AppSearchConfigured: React.FC = (props) => { - - - {errorConnecting ? ( - - ) : ( - - - - - - - - - - - - )} - - + } readOnlyMode={readOnlyMode}> + {errorConnecting ? ( + + ) : ( + + + + + + + + + + + + )} + ); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx index 237639fbf7beb..79a5cea12ef86 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/ml/anomaly_detection/flyout_home.tsx @@ -109,7 +109,7 @@ export const FlyoutHome = (props: Props) => {
- {hostJobSummaries.length > 0 && ( + {(hostJobSummaries.length > 0 || k8sJobSummaries.length > 0) && ( <> 0} diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/ingest_manager/common/types/index.ts index 49be48ef542f5..e9271c24456b5 100644 --- a/x-pack/plugins/ingest_manager/common/types/index.ts +++ b/x-pack/plugins/ingest_manager/common/types/index.ts @@ -9,6 +9,7 @@ export * from './rest_spec'; export interface IngestManagerConfigType { enabled: boolean; registryUrl?: string; + registryProxyUrl?: string; agents: { enabled: boolean; tlsCheckDisabled: boolean; diff --git a/x-pack/plugins/ingest_manager/common/types/models/output.ts b/x-pack/plugins/ingest_manager/common/types/models/output.ts index f3e76cd167b3f..a02c884270919 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/output.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/output.ts @@ -18,6 +18,7 @@ export interface NewOutput { fleet_enroll_username?: string; fleet_enroll_password?: string; config?: Record; + config_yaml?: string; } export type OutputSOAttributes = NewOutput; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts index 87e8a0977e3ba..bee4814e31b9e 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/output.ts @@ -22,6 +22,8 @@ export interface PutOutputRequest { body: { hosts?: string[]; ca_sha256?: string; + config?: Record; + config_yaml?: string; }; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx index bd26daeb4e879..a24640af7438f 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/enrollment_instructions/manual/index.tsx @@ -26,7 +26,7 @@ export const ManualInstructions: React.FunctionComponent = ({ apiKey, kibanaCASha256, }) => { - const enrollArgs = `${kibanaUrl} ${apiKey.api_key}${ + const enrollArgs = `--kibana-url=${kibanaUrl} --enrollment-token=${apiKey.api_key}${ kibanaCASha256 ? ` --ca_sha256=${kibanaCASha256}` : '' }`; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx index e0d843ad773b8..9e66fc7b37b57 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx @@ -20,10 +20,12 @@ import { EuiFormRow, EuiRadioGroup, EuiComboBox, + EuiCodeEditor, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText } from '@elastic/eui'; -import { useComboInput, useCore, useGetSettings, sendPutSettings } from '../hooks'; +import { safeLoad } from 'js-yaml'; +import { useComboInput, useCore, useGetSettings, useInput, sendPutSettings } from '../hooks'; import { useGetOutputs, sendPutOutput } from '../hooks/use_request/outputs'; import { isDiffPathProtocol } from '../../../../common/'; @@ -69,10 +71,27 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { } }); + const additionalYamlConfigInput = useInput('', (value) => { + try { + safeLoad(value); + return; + } catch (error) { + return [ + i18n.translate('xpack.ingestManager.settings.invalidYamlFormatErrorMessage', { + defaultMessage: 'Invalid YAML: {reason}', + values: { reason: error.message }, + }), + ]; + } + }); return { isLoading, onSubmit: async () => { - if (!kibanaUrlsInput.validate() || !elasticsearchUrlInput.validate()) { + if ( + !kibanaUrlsInput.validate() || + !elasticsearchUrlInput.validate() || + !additionalYamlConfigInput.validate() + ) { return; } @@ -83,6 +102,7 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { } const outputResponse = await sendPutOutput(outputId, { hosts: elasticsearchUrlInput.value, + config_yaml: additionalYamlConfigInput.value, }); if (outputResponse.error) { throw outputResponse.error; @@ -110,6 +130,7 @@ function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { inputs: { kibanaUrls: kibanaUrlsInput, elasticsearchUrl: elasticsearchUrlInput, + additionalYamlConfig: additionalYamlConfigInput, }, }; } @@ -124,6 +145,10 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { useEffect(() => { if (output) { inputs.elasticsearchUrl.setValue(output.hosts || []); + inputs.additionalYamlConfig.setValue( + output.config_yaml || + `# YAML settings here will be added to the Elasticsearch output section of each policy` + ); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [output]); @@ -247,6 +272,30 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { + + + + + + ); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx index 5d485a6e21086..fefb427df5ea6 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_yaml_flyout.tsx @@ -51,7 +51,9 @@ export const AgentPolicyYamlFlyout = memo<{ policyId: string; onClose: () => voi {error.message} ) : ( - + // Property 'whiteSpace' does not exist on type 'IntrinsicAttributes & CommonProps & OwnProps & HTMLAttributes & { children?: ReactNode; }'. + // @ts-expect-error linter complains whiteSpace isn't available but docs show it on EuiCodeBlockImpl + {fullAgentPolicyToYaml(yamlData!.item)} ); diff --git a/x-pack/plugins/ingest_manager/server/errors/handlers.ts b/x-pack/plugins/ingest_manager/server/errors/handlers.ts index bcad3f9c022da..ec237c4ca833b 100644 --- a/x-pack/plugins/ingest_manager/server/errors/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/errors/handlers.ts @@ -17,6 +17,7 @@ import { IngestManagerError, RegistryError, PackageNotFoundError, + AgentPolicyNameExistsError, PackageUnsupportedMediaTypeError, } from './index'; @@ -57,6 +58,9 @@ const getHTTPResponseCode = (error: IngestManagerError): number => { if (error instanceof PackageNotFoundError) { return 404; // Not Found } + if (error instanceof AgentPolicyNameExistsError) { + return 409; // Conflict + } if (error instanceof PackageUnsupportedMediaTypeError) { return 415; // Unsupported Media Type } diff --git a/x-pack/plugins/ingest_manager/server/errors/index.ts b/x-pack/plugins/ingest_manager/server/errors/index.ts index 15ac97f21a17a..e3ca6a9b48dcf 100644 --- a/x-pack/plugins/ingest_manager/server/errors/index.ts +++ b/x-pack/plugins/ingest_manager/server/errors/index.ts @@ -18,6 +18,8 @@ export class RegistryConnectionError extends RegistryError {} export class RegistryResponseError extends RegistryError {} export class PackageNotFoundError extends IngestManagerError {} export class PackageOutdatedError extends IngestManagerError {} +export class AgentPolicyError extends IngestManagerError {} +export class AgentPolicyNameExistsError extends AgentPolicyError {} export class PackageUnsupportedMediaTypeError extends IngestManagerError {} export class PackageInvalidArchiveError extends IngestManagerError {} export class PackageCacheError extends IngestManagerError {} diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/ingest_manager/server/index.ts index 70685cf818b08..ad2eecc0bb057 100644 --- a/x-pack/plugins/ingest_manager/server/index.ts +++ b/x-pack/plugins/ingest_manager/server/index.ts @@ -31,6 +31,7 @@ export const config: PluginConfigDescriptor = { schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), registryUrl: schema.maybe(schema.uri()), + registryProxyUrl: schema.maybe(schema.uri()), agents: schema.object({ enabled: schema.boolean({ defaultValue: true }), tlsCheckDisabled: schema.boolean({ defaultValue: false }), diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts index 5fc301ceab20f..8f1ece923f126 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts @@ -207,6 +207,7 @@ const getSavedObjectTypes = ( fleet_enroll_username: { type: 'binary' }, fleet_enroll_password: { type: 'binary' }, config: { type: 'flattened' }, + config_yaml: { type: 'text' }, }, }, }, @@ -347,6 +348,7 @@ export function registerEncryptedSavedObjects( 'hosts', 'ca_sha256', 'config', + 'config_yaml', ]), }); encryptedSavedObjects.registerType({ diff --git a/x-pack/plugins/ingest_manager/server/services/agent_policy.ts b/x-pack/plugins/ingest_manager/server/services/agent_policy.ts index 12ea8ab92f6c4..37d5d49fcbfef 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_policy.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_policy.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { uniq } from 'lodash'; +import { safeLoad } from 'js-yaml'; import { SavedObjectsClientContract, SavedObjectsBulkUpdateResponse } from 'src/core/server'; import { AuthenticatedUser } from '../../../security/server'; import { @@ -20,13 +21,18 @@ import { AgentPolicyStatus, ListWithKuery, } from '../types'; -import { DeleteAgentPolicyResponse, storedPackagePoliciesToAgentInputs } from '../../common'; +import { + DeleteAgentPolicyResponse, + Settings, + storedPackagePoliciesToAgentInputs, +} from '../../common'; +import { AgentPolicyNameExistsError } from '../errors'; import { createAgentPolicyAction, listAgents } from './agents'; import { packagePolicyService } from './package_policy'; import { outputService } from './output'; import { agentPolicyUpdateEventHandler } from './agent_policy_update'; import { getSettings } from './settings'; -import { normalizeKuery } from './saved_object'; +import { normalizeKuery, escapeSearchQueryPhrase } from './saved_object'; const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE; @@ -101,6 +107,7 @@ class AgentPolicyService { agentPolicy: NewAgentPolicy, options?: { id?: string; user?: AuthenticatedUser } ): Promise { + await this.requireUniqueName(soClient, agentPolicy); const newSo = await soClient.create( SAVED_OBJECT_TYPE, { @@ -119,6 +126,30 @@ class AgentPolicyService { return { id: newSo.id, ...newSo.attributes }; } + public async requireUniqueName( + soClient: SavedObjectsClientContract, + { name, namespace }: Pick + ) { + const results = await soClient.find({ + type: SAVED_OBJECT_TYPE, + searchFields: ['namespace', 'name'], + search: `${namespace} + ${escapeSearchQueryPhrase(name)}`, + }); + + if (results.total) { + const policies = results.saved_objects; + const isSinglePolicy = policies.length === 1; + const policyList = isSinglePolicy ? policies[0].id : policies.map(({ id }) => id).join(','); + const existClause = isSinglePolicy + ? `Agent Policy '${policyList}' already exists` + : `Agent Policies '${policyList}' already exist`; + + throw new AgentPolicyNameExistsError( + `${existClause} in '${namespace}' namespace with name '${name}'` + ); + } + } + public async get( soClient: SavedObjectsClientContract, id: string, @@ -204,6 +235,12 @@ class AgentPolicyService { agentPolicy: Partial, options?: { user?: AuthenticatedUser } ): Promise { + if (agentPolicy.name && agentPolicy.namespace) { + await this.requireUniqueName(soClient, { + name: agentPolicy.name, + namespace: agentPolicy.namespace, + }); + } return this._update(soClient, id, agentPolicy, options?.user); } @@ -452,13 +489,14 @@ class AgentPolicyService { // TEMPORARY as we only support a default output ...[defaultOutput].reduce( // eslint-disable-next-line @typescript-eslint/naming-convention - (outputs, { config: outputConfig, name, type, hosts, ca_sha256, api_key }) => { + (outputs, { config_yaml, name, type, hosts, ca_sha256, api_key }) => { + const configJs = config_yaml ? safeLoad(config_yaml) : {}; outputs[name] = { type, hosts, ca_sha256, api_key, - ...outputConfig, + ...configJs, }; if (options?.standalone) { @@ -494,7 +532,7 @@ class AgentPolicyService { // only add settings if not in standalone if (!standalone) { - let settings; + let settings: Settings; try { settings = await getSettings(soClient); } catch (error) { diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.test.ts new file mode 100644 index 0000000000000..d6e60eb11b230 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.test.ts @@ -0,0 +1,70 @@ +/* + * 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 HttpProxyAgent from 'http-proxy-agent'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import { getProxyAgent, getProxyAgentOptions } from './proxy'; + +describe('getProxyAgent', () => { + test('return HttpsProxyAgent for https proxy url', () => { + const agent = getProxyAgent({ + proxyUrl: 'https://proxyhost', + targetUrl: 'https://targethost', + }); + expect(agent instanceof HttpsProxyAgent).toBeTruthy(); + }); + + test('return HttpProxyAgent for http proxy url', () => { + const agent = getProxyAgent({ + proxyUrl: 'http://proxyhost', + targetUrl: 'http://targethost', + }); + expect(agent instanceof HttpProxyAgent).toBeTruthy(); + }); +}); + +describe('getProxyAgentOptions', () => { + test('return url only for https', () => { + const httpsProxy = 'https://12.34.56.78:910'; + + const optionsA = getProxyAgentOptions({ + proxyUrl: httpsProxy, + targetUrl: 'https://targethost', + }); + expect(optionsA).toEqual({ + headers: { Host: 'targethost' }, + host: '12.34.56.78', + port: 910, + protocol: 'https:', + rejectUnauthorized: undefined, + }); + + const optionsB = getProxyAgentOptions({ + proxyUrl: httpsProxy, + targetUrl: 'https://example.com/?a=b&c=d', + }); + expect(optionsB).toEqual({ + headers: { Host: 'example.com' }, + host: '12.34.56.78', + port: 910, + protocol: 'https:', + rejectUnauthorized: undefined, + }); + + // given http value and https proxy + const optionsC = getProxyAgentOptions({ + proxyUrl: httpsProxy, + targetUrl: 'http://example.com/?a=b&c=d', + }); + expect(optionsC).toEqual({ + headers: { Host: 'example.com' }, + host: '12.34.56.78', + port: 910, + protocol: 'https:', + rejectUnauthorized: undefined, + }); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.ts new file mode 100644 index 0000000000000..ae1c13761052b --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/proxy.ts @@ -0,0 +1,54 @@ +/* + * 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 HttpProxyAgent from 'http-proxy-agent'; +import HttpsProxyAgent, { + HttpsProxyAgent as IHttpsProxyAgent, + HttpsProxyAgentOptions, +} from 'https-proxy-agent'; +import { appContextService } from '../../index'; +export interface RegistryProxySettings { + proxyUrl: string; + proxyHeaders?: Record; + proxyRejectUnauthorizedCertificates?: boolean; +} + +type ProxyAgent = IHttpsProxyAgent | HttpProxyAgent; +type GetProxyAgentParams = RegistryProxySettings & { targetUrl: string }; + +export function getRegistryProxyUrl(): string | undefined { + const proxyUrl = appContextService.getConfig()?.registryProxyUrl; + return proxyUrl; +} + +export function getProxyAgent(options: GetProxyAgentParams): ProxyAgent { + const isHttps = options.targetUrl.startsWith('https:'); + const agentOptions = isHttps && getProxyAgentOptions(options); + const agent: ProxyAgent = isHttps + ? // @ts-expect-error ts(7009) HttpsProxyAgent isn't a class so TS complains about using `new` + new HttpsProxyAgent(agentOptions) + : new HttpProxyAgent(options.proxyUrl); + + return agent; +} + +export function getProxyAgentOptions(options: GetProxyAgentParams): HttpsProxyAgentOptions { + const endpointParsed = new URL(options.targetUrl); + const proxyParsed = new URL(options.proxyUrl); + + return { + host: proxyParsed.hostname, + port: Number(proxyParsed.port), + protocol: proxyParsed.protocol, + // The headers to send + headers: options.proxyHeaders || { + // the proxied URL's host is put in the header instead of the server's actual host + Host: endpointParsed.host, + }, + // do not fail on invalid certs if value is false + rejectUnauthorized: options.proxyRejectUnauthorizedCertificates, + }; +} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts b/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts index e549d6b1f71aa..2b9c349565790 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/registry/requests.ts @@ -4,17 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import fetch, { FetchError, Response } from 'node-fetch'; +import fetch, { FetchError, Response, RequestInit } from 'node-fetch'; import pRetry from 'p-retry'; import { streamToString } from './streams'; +import { appContextService } from '../../app_context'; import { RegistryError, RegistryConnectionError, RegistryResponseError } from '../../../errors'; +import { getProxyAgent, getRegistryProxyUrl } from './proxy'; type FailedAttemptErrors = pRetry.FailedAttemptError | FetchError | Error; // not sure what to call this function, but we're not exporting it async function registryFetch(url: string) { - const response = await fetch(url); - + const response = await fetch(url, getFetchOptions(url)); if (response.ok) { return response; } else { @@ -81,3 +82,17 @@ function isFetchError(error: FailedAttemptErrors): error is FetchError { function isSystemError(error: FailedAttemptErrors): boolean { return isFetchError(error) && error.type === 'system'; } + +export function getFetchOptions(targetUrl: string): RequestInit | undefined { + const proxyUrl = getRegistryProxyUrl(); + if (!proxyUrl) { + return undefined; + } + + const logger = appContextService.getLogger(); + logger.debug(`Using ${proxyUrl} as proxy for ${targetUrl}`); + + return { + agent: getProxyAgent({ proxyUrl, targetUrl }), + }; +} diff --git a/x-pack/plugins/ingest_manager/server/types/models/output.ts b/x-pack/plugins/ingest_manager/server/types/models/output.ts index 22a101ecd94b8..c5aab6395adab 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/output.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/output.ts @@ -17,6 +17,7 @@ const OutputBaseSchema = { fleet_enroll_username: schema.maybe(schema.string()), fleet_enroll_password: schema.maybe(schema.string()), config: schema.maybe(schema.recordOf(schema.string(), schema.any())), + config_yaml: schema.maybe(schema.string()), }; export const NewOutputSchema = schema.object({ diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts index 315923fd9f401..bf8c98e6cabc9 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts @@ -20,5 +20,7 @@ export const PutOutputRequestSchema = { body: schema.object({ hosts: schema.maybe(schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }))), ca_sha256: schema.maybe(schema.string()), + config: schema.maybe(schema.recordOf(schema.string(), schema.any())), + config_yaml: schema.maybe(schema.string()), }), }; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts index 35718491c9224..18cbd8437d8e2 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts @@ -23,5 +23,6 @@ export const PutSettingsRequestSchema = { ), kibana_ca_sha256: schema.maybe(schema.string()), has_seen_add_data_notice: schema.maybe(schema.boolean()), + additional_yaml_config: schema.maybe(schema.string()), }), }; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap b/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap index dd62be11c679d..120ff2e7adde3 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap @@ -67,84 +67,6 @@ exports[`scaling form should disable clusters option when clustering is not supp > - -
- - - - - -`; - -exports[`scaling form should disable mvt option when mvt is not supported 1`] = ` - - -
- -
-
- - -
- - - - - @@ -162,8 +159,6 @@ export class CreateSourceEditor extends Component { this.state.indexPattern, this.state.geoFieldName )} - supportsMvt={mvtSupported} - mvtDisabledReason={mvtSupported ? null : getMvtDisabledReason()} clusteringDisabledReason={ this.state.indexPattern ? getGeoTileAggNotSupportedReason( diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js index edcafae54d54c..da75dfd83d809 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.js @@ -614,7 +614,9 @@ export class ESSearchSource extends AbstractESSource { `/${GIS_API_PATH}/${MVT_GETTILE_API_PATH}` ); - const urlTemplate = `${mvtUrlServicePath}?x={x}&y={y}&z={z}&geometryFieldName=${this._descriptor.geoField}&index=${indexPattern.title}&requestBody=${risonDsl}`; + const geoField = await this._getGeoField(); + + const urlTemplate = `${mvtUrlServicePath}?x={x}&y={y}&z={z}&geometryFieldName=${this._descriptor.geoField}&index=${indexPattern.title}&requestBody=${risonDsl}&geoFieldType=${geoField.type}`; return { layerName: this.getLayerName(), minSourceZoom: this.getMinZoom(), diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts index 0bc9bba7816ca..9c1cda4088dcd 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts @@ -110,7 +110,7 @@ describe('ESSearchSource', () => { ); const urlTemplateWithMeta = await esSearchSource.getUrlTemplateWithMeta(searchFilters); expect(urlTemplateWithMeta.urlTemplate).toBe( - `rootdir/api/maps/mvt/getTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fields,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))` + `rootdir/api/maps/mvt/getTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fields,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape` ); }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx index f57335db14c62..867e56f3e24bd 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.test.tsx @@ -27,8 +27,6 @@ const defaultProps = { termFields: [], topHitsSplitField: null, topHitsSize: 1, - supportsMvt: true, - mvtDisabledReason: null, }; describe('scaling form', () => { @@ -57,16 +55,4 @@ describe('scaling form', () => { expect(component).toMatchSnapshot(); }); - - test('should disable mvt option when mvt is not supported', async () => { - const component = shallow( - - ); - - expect(component).toMatchSnapshot(); - }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx index cc2d4d059a3a8..370c3f8923ea0 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/scaling_form.tsx @@ -39,9 +39,7 @@ interface Props { onChange: (args: OnSourceChangeArgs) => void; scalingType: SCALING_TYPES; supportsClustering: boolean; - supportsMvt: boolean; clusteringDisabledReason?: string | null; - mvtDisabledReason?: string | null; termFields: IFieldType[]; topHitsSplitField: string | null; topHitsSize: number; @@ -197,7 +195,6 @@ export class ScalingForm extends Component { label={labelText} checked={this.props.scalingType === SCALING_TYPES.MVT} onChange={() => this._onScalingTypeChange(SCALING_TYPES.MVT)} - disabled={!this.props.supportsMvt} /> ); @@ -211,11 +208,7 @@ export class ScalingForm extends Component { ); - return !this.props.supportsMvt ? ( - - {mvtRadio} - - ) : ( + return ( {mvtRadio} diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js b/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js index c123c307c4895..735510102e25e 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/update_source_editor.js @@ -17,8 +17,6 @@ import { getTermsFields, getSourceFields, supportsGeoTileAgg, - supportsMvt, - getMvtDisabledReason, } from '../../../index_pattern_util'; import { SORT_ORDER } from '../../../../common/constants'; import { ESDocField } from '../../fields/es_doc_field'; @@ -44,7 +42,6 @@ export class UpdateSourceEditor extends Component { termFields: null, sortFields: null, supportsClustering: false, - supportsMvt: false, mvtDisabledReason: null, clusteringDisabledReason: null, }; @@ -99,12 +96,10 @@ export class UpdateSourceEditor extends Component { }); }); - const mvtSupported = supportsMvt(indexPattern, geoField.name); this.setState({ supportsClustering: supportsGeoTileAgg(geoField), - supportsMvt: mvtSupported, clusteringDisabledReason: getGeoTileAggNotSupportedReason(geoField), - mvtDisabledReason: mvtSupported ? null : getMvtDisabledReason(), + mvtDisabledReason: null, sourceFields: sourceFields, termFields: getTermsFields(indexPattern.fields), //todo change term fields to use fields sortFields: indexPattern.fields.filter( @@ -215,9 +210,7 @@ export class UpdateSourceEditor extends Component { onChange={this.props.onChange} scalingType={this.props.scalingType} supportsClustering={this.state.supportsClustering} - supportsMvt={this.state.supportsMvt} clusteringDisabledReason={this.state.clusteringDisabledReason} - mvtDisabledReason={this.state.mvtDisabledReason} termFields={this.state.termFields} topHitsSplitField={this.props.topHitsSplitField} topHitsSize={this.props.topHitsSize} diff --git a/x-pack/plugins/maps/public/index_pattern_util.ts b/x-pack/plugins/maps/public/index_pattern_util.ts index 7af1571a0bc5b..68fd224dcbb45 100644 --- a/x-pack/plugins/maps/public/index_pattern_util.ts +++ b/x-pack/plugins/maps/public/index_pattern_util.ts @@ -82,17 +82,6 @@ export function supportsGeoTileAgg(field?: IFieldType): boolean { ); } -export function supportsMvt(indexPattern: IndexPattern, geoFieldName: string): boolean { - const field = indexPattern.fields.getByName(geoFieldName); - return !!field && field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE; -} - -export function getMvtDisabledReason() { - return i18n.translate('xpack.maps.mbt.disabled', { - defaultMessage: 'Display as vector tiles is only supported for geo_shape field-types.', - }); -} -// Returns filtered fields list containing only fields that exist in _source. export function getSourceFields(fields: IFieldType[]): IFieldType[] { return fields.filter((field) => { // Multi fields are not stored in _source and only exist in index. diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts index 9bced75b613d7..9fbe090633747 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/index.ts @@ -7,7 +7,7 @@ import { AnyAction } from 'redux'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { IndexPatternsContract } from 'src/plugins/data/public/index_patterns'; -import { AppMountContext, AppMountParameters } from 'kibana/public'; +import { AppMountParameters } from 'kibana/public'; import { IndexPattern } from 'src/plugins/data/public'; import { Embeddable, IContainer } from '../../../../../src/plugins/embeddable/public'; import { LayerDescriptor } from '../../common/descriptor_types'; @@ -40,7 +40,7 @@ interface LazyLoadedMapModules { initialLayers?: LayerDescriptor[] ) => LayerDescriptor[]; mergeInputWithSavedMap: any; - renderApp: (context: AppMountContext, params: AppMountParameters) => Promise<() => void>; + renderApp: (params: AppMountParameters) => Promise<() => void>; createSecurityLayerDescriptors: ( indexPatternId: string, indexPatternTitle: string diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 5b79863d0dd97..a2b629bdd4989 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -9,6 +9,7 @@ import { UiActionsStart } from 'src/plugins/ui_actions/public'; import { NavigationPublicPluginStart } from 'src/plugins/navigation/public'; import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { + AppMountParameters, CoreSetup, CoreStart, Plugin, @@ -131,9 +132,9 @@ export class MapsPlugin icon: `plugins/${APP_ID}/icon.svg`, euiIconType: APP_ICON_SOLUTION, category: DEFAULT_APP_CATEGORIES.kibana, - async mount(context, params) { + async mount(params: AppMountParameters) { const { renderApp } = await lazyLoadMapModules(); - return renderApp(context, params); + return renderApp(params); }, }); } diff --git a/x-pack/plugins/maps/public/routing/maps_router.tsx b/x-pack/plugins/maps/public/routing/maps_router.tsx index a28c293a2f32f..d7e6e6e079953 100644 --- a/x-pack/plugins/maps/public/routing/maps_router.tsx +++ b/x-pack/plugins/maps/public/routing/maps_router.tsx @@ -9,7 +9,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { Router, Switch, Route, Redirect, RouteComponentProps } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { Provider } from 'react-redux'; -import { AppMountContext, AppMountParameters } from 'kibana/public'; +import { AppMountParameters } from 'kibana/public'; import { getCoreChrome, getCoreI18n, @@ -29,10 +29,13 @@ import { LoadMapAndRender } from './routes/maps_app/load_map_and_render'; export let goToSpecifiedPath: (path: string) => void; export let kbnUrlStateStorage: IKbnUrlStateStorage; -export async function renderApp( - context: AppMountContext, - { appBasePath, element, history, onAppLeave, setHeaderActionMenu }: AppMountParameters -) { +export async function renderApp({ + appBasePath, + element, + history, + onAppLeave, + setHeaderActionMenu, +}: AppMountParameters) { goToSpecifiedPath = (path) => history.push(path); kbnUrlStateStorage = createKbnUrlStateStorage({ useHash: false, diff --git a/x-pack/plugins/maps/server/mvt/get_tile.test.ts b/x-pack/plugins/maps/server/mvt/get_tile.test.ts index 76c1741ab2ad0..1e00fd27e3d1b 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.test.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.test.ts @@ -50,6 +50,7 @@ describe('getTile', () => { info: () => {}, } as unknown) as Logger, callElasticsearch: mockCallElasticsearch, + geoFieldType: ES_GEO_FIELD_TYPE.GEO_SHAPE, }); compareTiles('./__tests__/pbf/0_0_0_docs.pbf', tile); diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts index dd88be7f80c2e..cc87f3b65522e 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -21,9 +21,8 @@ import { SUPER_FINE_ZOOM_DELTA, } from '../../common/constants'; -import { hitsToGeoJson } from '../../common/elasticsearch_util'; +import { convertRegularRespToGeoJson, hitsToGeoJson } from '../../common/elasticsearch_util'; import { flattenHit } from './util'; -import { convertRegularRespToGeoJson } from '../../common/elasticsearch_util'; import { ESBounds, tile2lat, tile2long, tileToESBbox } from '../../common/geo_tile_utils'; export async function getGridTile({ @@ -107,6 +106,7 @@ export async function getTile({ y, z, requestBody = {}, + geoFieldType, }: { x: number; y: number; @@ -116,6 +116,7 @@ export async function getTile({ callElasticsearch: (type: string, ...args: any[]) => Promise; logger: Logger; requestBody: any; + geoFieldType: ES_GEO_FIELD_TYPE; }): Promise { const geojsonBbox = tileToGeoJsonPolygon(x, y, z); @@ -181,7 +182,6 @@ export async function getTile({ }, ]; } else { - // Perform actual search result = await callElasticsearch('search', esSearchQuery); // Todo: pass in epochMillies-fields @@ -192,7 +192,7 @@ export async function getTile({ return flattenHit(geometryFieldName, hit); }, geometryFieldName, - ES_GEO_FIELD_TYPE.GEO_SHAPE, + geoFieldType, [] ); diff --git a/x-pack/plugins/maps/server/mvt/mvt_routes.ts b/x-pack/plugins/maps/server/mvt/mvt_routes.ts index 266a240b53017..fc298f73b04a5 100644 --- a/x-pack/plugins/maps/server/mvt/mvt_routes.ts +++ b/x-pack/plugins/maps/server/mvt/mvt_routes.ts @@ -36,6 +36,7 @@ export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRou geometryFieldName: schema.string(), requestBody: schema.string(), index: schema.string(), + geoFieldType: schema.string(), }), }, }, @@ -56,6 +57,7 @@ export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRou z: query.z as number, index: query.index as string, requestBody: requestBodyDSL as any, + geoFieldType: query.geoFieldType as ES_GEO_FIELD_TYPE, }); return sendResponse(response, tile); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index c1f6d75637ed4..bc85153928a4b 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -38,7 +38,9 @@ export function loadFullJob(jobId) { } export function isStartable(jobs) { - return jobs.some((j) => j.datafeedState === DATAFEED_STATE.STOPPED); + return jobs.some( + (j) => j.datafeedState === DATAFEED_STATE.STOPPED && j.jobState !== JOB_STATE.CLOSING + ); } export function isStoppable(jobs) { @@ -49,7 +51,10 @@ export function isStoppable(jobs) { export function isClosable(jobs) { return jobs.some( - (j) => j.datafeedState === DATAFEED_STATE.STOPPED && j.jobState !== JOB_STATE.CLOSED + (j) => + j.datafeedState === DATAFEED_STATE.STOPPED && + j.jobState !== JOB_STATE.CLOSED && + j.jobState !== JOB_STATE.CLOSING ); } diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index c7e5cee1ed18c..af1aaf16f7fed 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -427,10 +427,10 @@ describe('#bulkCreate', () => { await expectPrivilegeCheck(client.bulkCreate, { objects, options }, [namespace]); }); - test(`checks privileges for user, actions, namespace, and namespaces`, async () => { + test(`checks privileges for user, actions, namespace, and initialNamespaces`, async () => { const objects = [ - { ...obj1, namespaces: 'another-ns' }, - { ...obj2, namespaces: 'yet-another-ns' }, + { ...obj1, initialNamespaces: 'another-ns' }, + { ...obj2, initialNamespaces: 'yet-another-ns' }, ]; const options = { namespace }; await expectPrivilegeCheck(client.bulkCreate, { objects, options }, [ @@ -601,8 +601,8 @@ describe('#create', () => { await expectPrivilegeCheck(client.create, { type, attributes, options }, [namespace]); }); - test(`checks privileges for user, actions, namespace, and namespaces`, async () => { - const options = { namespace, namespaces: ['another-ns', 'yet-another-ns'] }; + test(`checks privileges for user, actions, namespace, and initialNamespaces`, async () => { + const options = { namespace, initialNamespaces: ['another-ns', 'yet-another-ns'] }; await expectPrivilegeCheck(client.create, { type, attributes, options }, [ namespace, 'another-ns', diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index ad0bc085eb8e2..d94dac942845e 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -86,7 +86,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra options: SavedObjectsCreateOptions = {} ) { const args = { type, attributes, options }; - const namespaces = [options.namespace, ...(options.namespaces || [])]; + const namespaces = [options.namespace, ...(options.initialNamespaces || [])]; await this.ensureAuthorized(type, 'create', namespaces, { args }); const savedObject = await this.baseClient.create(type, attributes, options); @@ -114,7 +114,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra ) { const args = { objects, options }; const namespaces = objects.reduce( - (acc, { namespaces: initialNamespaces = [] }) => { + (acc, { initialNamespaces = [] }) => { return acc.concat(initialNamespaces); }, [options.namespace] diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 079c18b6abe6e..1433acd27c930 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -190,16 +190,16 @@ export const fillDefineCustomRuleWithImportedQueryAndContinue = ( ) => { cy.get(IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK).click(); cy.get(TIMELINE(rule.timelineId)).click(); - cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', rule.customQuery); + cy.get(CUSTOM_QUERY_INPUT).should('have.text', rule.customQuery); cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); cy.get(CUSTOM_QUERY_INPUT).should('not.exist'); }; export const fillScheduleRuleAndContinue = (rule: CustomRule | MachineLearningRule) => { - cy.get(RUNS_EVERY_INTERVAL).clear().type(rule.runsEvery.interval); + cy.get(RUNS_EVERY_INTERVAL).type('{selectall}').type(rule.runsEvery.interval); cy.get(RUNS_EVERY_TIME_TYPE).select(rule.runsEvery.timeType); - cy.get(LOOK_BACK_INTERVAL).clear().type(rule.lookBack.interval); + cy.get(LOOK_BACK_INTERVAL).type('{selectAll}').type(rule.lookBack.interval); cy.get(LOOK_BACK_TIME_TYPE).select(rule.lookBack.timeType); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/add_item_form/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/add_item_form/index.tsx index b0098d62cc9e5..d0e606ef368a1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/add_item_form/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/add_item_form/index.tsx @@ -147,10 +147,7 @@ export const AddItem = ({ ...(index === values.length - 1 ? { inputRef: handleLastInputRef.bind(null, index) } : {}), - ...((inputsRef.current[index] != null && inputsRef.current[index].value !== item) || - inputsRef.current[index] == null - ? { value: item } - : {}), + value: item, isInvalid: validate == null ? false : showValidation && validate(item), }; return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.test.tsx index 9dddd9e6c4085..7a5307f650ebc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.test.tsx @@ -5,27 +5,91 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount, shallow } from 'enzyme'; import { ScheduleItem } from './index'; -import { useFormFieldMock } from '../../../../common/mock'; +import { TestProviders, useFormFieldMock } from '../../../../common/mock'; describe('ScheduleItem', () => { it('renders correctly', () => { - const Component = () => { - const field = useFormFieldMock(); + const mockField = useFormFieldMock(); + const wrapper = shallow( + + ); - return ( + expect(wrapper.find('[data-test-subj="schedule-item"]')).toHaveLength(1); + }); + + it('accepts a large number via user input', () => { + const mockField = useFormFieldMock(); + const wrapper = mount( + + + + ); + + wrapper + .find('[data-test-subj="interval"]') + .last() + .simulate('change', { target: { value: '5000000' } }); + + expect(mockField.setValue).toHaveBeenCalledWith('5000000s'); + }); + + it('clamps a number value greater than MAX_SAFE_INTEGER to MAX_SAFE_INTEGER', () => { + const unsafeInput = '99999999999999999999999'; + + const mockField = useFormFieldMock(); + const wrapper = mount( + - ); - }; - const wrapper = shallow(); + + ); + + wrapper + .find('[data-test-subj="interval"]') + .last() + .simulate('change', { target: { value: unsafeInput } }); + + const expectedValue = `${Number.MAX_SAFE_INTEGER}s`; + expect(mockField.setValue).toHaveBeenCalledWith(expectedValue); + }); + + it('converts a non-numeric value to 0', () => { + const unsafeInput = 'this is not a number'; + + const mockField = useFormFieldMock(); + const wrapper = mount( + + + + ); + + wrapper + .find('[data-test-subj="interval"]') + .last() + .simulate('change', { target: { value: unsafeInput } }); - expect(wrapper.dive().find('[data-test-subj="schedule-item"]')).toHaveLength(1); + expect(mockField.setValue).toHaveBeenCalledWith('0s'); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.tsx index ae012774fa30d..10d6325b99cbb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/schedule_item_form/index.tsx @@ -21,7 +21,7 @@ import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../shared_i import * as I18n from './translations'; interface ScheduleItemProps { - field: FieldHook; + field: FieldHook; dataTestSubj: string; idAria: string; isDisabled: boolean; @@ -62,6 +62,15 @@ const MyEuiSelect = styled(EuiSelect)` width: auto; `; +const getNumberFromUserInput = (input: string, defaultValue = 0): number => { + const number = parseInt(input, 10); + if (Number.isNaN(number)) { + return defaultValue; + } else { + return Math.min(number, Number.MAX_SAFE_INTEGER); + } +}; + export const ScheduleItem = ({ dataTestSubj, field, @@ -72,30 +81,29 @@ export const ScheduleItem = ({ const [timeType, setTimeType] = useState('s'); const [timeVal, setTimeVal] = useState(0); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const { value, setValue } = field; const onChangeTimeType = useCallback( (e) => { setTimeType(e.target.value); - field.setValue(`${timeVal}${e.target.value}`); + setValue(`${timeVal}${e.target.value}`); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [timeVal] + [setValue, timeVal] ); const onChangeTimeVal = useCallback( (e) => { - const sanitizedValue: number = parseInt(e.target.value, 10); + const sanitizedValue = getNumberFromUserInput(e.target.value, minimumValue); setTimeVal(sanitizedValue); - field.setValue(`${sanitizedValue}${timeType}`); + setValue(`${sanitizedValue}${timeType}`); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [timeType] + [minimumValue, setValue, timeType] ); useEffect(() => { - if (field.value !== `${timeVal}${timeType}`) { - const filterTimeVal = (field.value as string).match(/\d+/g); - const filterTimeType = (field.value as string).match(/[a-zA-Z]+/g); + if (value !== `${timeVal}${timeType}`) { + const filterTimeVal = value.match(/\d+/g); + const filterTimeType = value.match(/[a-zA-Z]+/g); if ( !isEmpty(filterTimeVal) && filterTimeVal != null && @@ -113,8 +121,7 @@ export const ScheduleItem = ({ setTimeType(filterTimeType[0]); } } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [field.value]); + }, [timeType, timeVal, value]); // EUI missing some props const rest = { disabled: isDisabled }; @@ -157,6 +164,7 @@ export const ScheduleItem = ({ { - // I do this to avoid the messy warning from happening - // Warning: React does not recognize the `isVisible` prop on a DOM element. - beforeEach(() => { - jest.spyOn(console, 'error').mockImplementation(jest.fn()); - }); - - afterEach(() => { - jest.spyOn(console, 'error').mockRestore(); - }); - it('renders correctly', () => { const Component = () => { const field = useFormFieldMock(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx index c365982db9cd8..aa1db1e31170e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx @@ -56,18 +56,16 @@ export const SelectRuleType: React.FC = ({ () => ({ onClick: setEql, isSelected: isEqlRule(ruleType), - isVisible: !isUpdateView || isEqlRule(ruleType), }), - [ruleType, setEql, isUpdateView] + [ruleType, setEql] ); const querySelectableConfig = useMemo( () => ({ onClick: setQuery, isSelected: isQueryRule(ruleType), - isVisible: !isUpdateView || isQueryRule(ruleType), }), - [ruleType, setQuery, isUpdateView] + [ruleType, setQuery] ); const mlSelectableConfig = useMemo( @@ -75,27 +73,24 @@ export const SelectRuleType: React.FC = ({ isDisabled: !hasValidLicense || !isMlAdmin, onClick: setMl, isSelected: isMlRule(ruleType), - isVisible: !isUpdateView || isMlRule(ruleType), }), - [ruleType, setMl, isUpdateView, hasValidLicense, isMlAdmin] + [ruleType, setMl, hasValidLicense, isMlAdmin] ); const thresholdSelectableConfig = useMemo( () => ({ onClick: setThreshold, isSelected: isThresholdRule(ruleType), - isVisible: !isUpdateView || isThresholdRule(ruleType), }), - [ruleType, setThreshold, isUpdateView] + [ruleType, setThreshold] ); const threatMatchSelectableConfig = useMemo( () => ({ onClick: setThreatMatch, isSelected: isThreatMatchRule(ruleType), - isVisible: !isUpdateView || isThreatMatchRule(ruleType), }), - [ruleType, setThreatMatch, isUpdateView] + [ruleType, setThreatMatch] ); return ( @@ -106,7 +101,7 @@ export const SelectRuleType: React.FC = ({ label={field.label} > - {querySelectableConfig.isVisible && ( + {(!isUpdateView || querySelectableConfig.isSelected) && ( = ({ /> )} - {mlSelectableConfig.isVisible && ( + {(!isUpdateView || mlSelectableConfig.isSelected) && ( = ({ /> )} - {thresholdSelectableConfig.isVisible && ( + {(!isUpdateView || thresholdSelectableConfig.isSelected) && ( = ({ /> )} - {eqlSelectableConfig.isVisible && ( + {(!isUpdateView || eqlSelectableConfig.isSelected) && ( = ({ /> )} - {threatMatchSelectableConfig.isVisible && ( + {(!isUpdateView || threatMatchSelectableConfig.isSelected) && ( = ({ 'data-test-subj': 'detectionEngineStepAboutRuleLicense', euiFieldProps: { fullWidth: true, - isDisabled: isLoading, + disabled: isLoading, placeholder: '', }, }} diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx index f8ed57aa7537e..1d2c2645034bf 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/index.test.tsx @@ -5,8 +5,7 @@ */ import React from 'react'; -import { shallow, mount, ReactWrapper } from 'enzyme'; -import { act } from 'react-dom/test-utils'; +import { shallow, mount } from 'enzyme'; import '../../../../../common/mock/match_media'; import '../../../../../common/mock/formatted_relative'; @@ -181,9 +180,8 @@ describe('AllRules', () => { }); describe('rules tab', () => { - let wrapper: ReactWrapper; - beforeEach(() => { - wrapper = mount( + it('renders correctly', async () => { + const wrapper = mount( { /> ); - }); - it('renders correctly', async () => { - await act(async () => { - await waitFor(() => { - expect(wrapper.exists('[data-test-subj="monitoring-table"]')).toBeFalsy(); - expect(wrapper.exists('[data-test-subj="rules-table"]')).toBeTruthy(); - }); + await waitFor(() => { + expect(wrapper.exists('[data-test-subj="monitoring-table"]')).toBeFalsy(); + expect(wrapper.exists('[data-test-subj="rules-table"]')).toBeTruthy(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 7639b878b9c5c..ba3a90bbc7bdb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -61,6 +61,7 @@ import { AdminSearchBar } from './components/search_bar'; import { AdministrationListPage } from '../../../components/administration_list_page'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { APP_ID } from '../../../../../common/constants'; +import { LinkToApp } from '../../../../common/components/endpoint/link_to_app'; const EndpointListNavLink = memo<{ name: string; @@ -578,16 +579,32 @@ export const EndpointList = () => { <> {areEndpointsEnrolling && !hasErrorFindingTotals && ( <> - - } - /> + + + + + ), + }} + /> + )} diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/components/condition_entry.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/components/condition_entry.tsx index 55d0622c3f9a9..020ab3f1cf1fd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/components/condition_entry.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/logical_condition/components/condition_entry.tsx @@ -16,6 +16,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { TrustedApp } from '../../../../../../../../common/endpoint/types'; +import { CONDITION_FIELD_TITLE } from '../../../translations'; const ConditionEntryCell = memo<{ showLabel: boolean; @@ -72,17 +73,11 @@ export const ConditionEntry = memo( const fieldOptions = useMemo>>(() => { return [ { - inputDisplay: i18n.translate( - 'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.hash', - { defaultMessage: 'Hash' } - ), + inputDisplay: CONDITION_FIELD_TITLE['process.hash.*'], value: 'process.hash.*', }, { - inputDisplay: i18n.translate( - 'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.path', - { defaultMessage: 'Path' } - ), + inputDisplay: CONDITION_FIELD_TITLE['process.executable.caseless'], value: 'process.executable.caseless', }, ]; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/__snapshots__/index.test.tsx.snap index c5a10c740ec7e..8f04fa0a7133a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/__snapshots__/index.test.tsx.snap @@ -36,6 +36,7 @@ exports[`trusted_app_card TrustedAppCard should render correctly 1`] = ` Object { "field": "field", "name": "Field", + "render": [Function], "sortable": false, "textOnly": true, "truncateText": true, @@ -44,6 +45,7 @@ exports[`trusted_app_card TrustedAppCard should render correctly 1`] = ` Object { "field": "operator", "name": "Operator", + "render": [Function], "sortable": false, "truncateText": true, "width": "20%", @@ -107,6 +109,7 @@ exports[`trusted_app_card TrustedAppCard should trim long descriptions 1`] = ` Object { "field": "field", "name": "Field", + "render": [Function], "sortable": false, "textOnly": true, "truncateText": true, @@ -115,6 +118,7 @@ exports[`trusted_app_card TrustedAppCard should trim long descriptions 1`] = ` Object { "field": "operator", "name": "Operator", + "render": [Function], "sortable": false, "truncateText": true, "width": "20%", diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx index 438331b706cc3..e31ff9e3b313b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_app_card/index.tsx @@ -7,6 +7,7 @@ import React, { memo, useCallback, useMemo } from 'react'; import { EuiTableFieldDataColumnType } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { Immutable, TrustedApp, @@ -27,6 +28,7 @@ import { PROPERTY_TITLES, ENTRY_PROPERTY_TITLES, CARD_DELETE_BUTTON_LABEL, + CONDITION_FIELD_TITLE, } from '../../translations'; type Entry = MacosLinuxConditionEntry | WindowsConditionEntry; @@ -47,6 +49,9 @@ const getEntriesColumnDefinitions = (): Array truncateText: true, textOnly: true, width: '30%', + render(field: MacosLinuxConditionEntry['field'], entry: Entry) { + return CONDITION_FIELD_TITLE[field]; + }, }, { field: 'operator', @@ -54,6 +59,11 @@ const getEntriesColumnDefinitions = (): Array sortable: false, truncateText: true, width: '20%', + render() { + return i18n.translate('xpack.securitySolution.trustedapps.card.operator.includes', { + defaultMessage: 'is', + }); + }, }, { field: 'value', diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx index dd735f7d75152..4664727dd848c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx @@ -54,9 +54,10 @@ const PaginationBar = ({ pagination, onChange }: PaginationBarProps) => { itemsPerPage={pagination.pageSize} itemsPerPageOptions={pagination.pageSizeOptions} pageCount={pageCount} - onChangeItemsPerPage={useCallback((size) => ({ index: 0, size }), [])} - onChangePage={useCallback((index) => ({ index, size: pagination.pageSize }), [ + onChangeItemsPerPage={useCallback((size) => onChange({ index: 0, size }), [onChange])} + onChangePage={useCallback((index) => onChange({ index, size: pagination.pageSize }), [ pagination.pageSize, + onChange, ])} />
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts index c6990159943e6..b442704169d06 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts @@ -28,6 +28,17 @@ export const OS_TITLES: Readonly<{ [K in TrustedApp['os']]: string }> = { }), }; +export const CONDITION_FIELD_TITLE: { [K in MacosLinuxConditionEntry['field']]: string } = { + 'process.hash.*': i18n.translate( + 'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.hash', + { defaultMessage: 'Hash' } + ), + 'process.executable.caseless': i18n.translate( + 'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.path', + { defaultMessage: 'Path' } + ), +}; + export const PROPERTY_TITLES: Readonly< { [K in keyof Omit]: string } > = { diff --git a/x-pack/plugins/security_solution/public/network/components/tls_table/columns.tsx b/x-pack/plugins/security_solution/public/network/components/tls_table/columns.tsx index 94de71017d339..904aed059bc55 100644 --- a/x-pack/plugins/security_solution/public/network/components/tls_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/network/components/tls_table/columns.tsx @@ -64,7 +64,7 @@ export const getTlsColumns = (tableId: string): TlsColumns => [ render: (sha1) => getRowItemDraggable({ rowItem: sha1, - attrName: 'tls.server_certificate.fingerprint.sha1', + attrName: 'tls.server.hash.sha1', idPrefix: `${tableId}-${sha1}-table-sha1`, }), }, @@ -77,7 +77,7 @@ export const getTlsColumns = (tableId: string): TlsColumns => [ render: ({ _id, ja3 }) => getRowItemDraggables({ rowItems: ja3, - attrName: 'tls.fingerprints.ja3.hash', + attrName: 'tls.server.ja3s', idPrefix: `${tableId}-${_id}-table-ja3`, }), }, @@ -90,7 +90,7 @@ export const getTlsColumns = (tableId: string): TlsColumns => [ render: ({ _id, notAfter }) => getRowItemDraggables({ rowItems: notAfter, - attrName: 'tls.server_certificate.not_after', + attrName: 'tls.server.not_after', idPrefix: `${tableId}-${_id}-table-notAfter`, render: (validUntil) => ( diff --git a/x-pack/plugins/security_solution/public/resolver/view/styles.tsx b/x-pack/plugins/security_solution/public/resolver/view/styles.tsx index e734d88fbd4ff..7b94445ee083f 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/styles.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/styles.tsx @@ -25,7 +25,7 @@ export const NodeSubMenu = styled(NodeSubMenuComponents)` background: transparent; position: absolute; top: 4.5em; - contain: content; + overflow-x: visible; width: 12em; z-index: 2; } diff --git a/x-pack/test/api_integration/apis/maps/get_tile.js b/x-pack/test/api_integration/apis/maps/get_tile.js index 7219fc858e059..9dd0698b6c6ed 100644 --- a/x-pack/test/api_integration/apis/maps/get_tile.js +++ b/x-pack/test/api_integration/apis/maps/get_tile.js @@ -11,7 +11,7 @@ export default function ({ getService }) { it('should validate params', async () => { await supertest .get( - `/api/maps/mvt/getTile?x=15&y=11&z=5&geometryFieldName=coordinates&index=logstash*&requestBody=(_source:(includes:!(coordinates)),docvalue_fields:!(),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),script_fields:(),size:10000,stored_fields:!(coordinates))` + `/api/maps/mvt/getTile?x=15&y=11&z=5&geometryFieldName=coordinates&index=logstash*&requestBody=(_source:(includes:!(coordinates)),docvalue_fields:!(),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),script_fields:(),size:10000,stored_fields:!(coordinates))&geoFieldType=geo_point` ) .set('kbn-xsrf', 'kibana') .expect(200); diff --git a/x-pack/test/functional/apps/maps/mvt_scaling.js b/x-pack/test/functional/apps/maps/mvt_scaling.js index 46ae2c0b9463a..b5c9ddcbd5e13 100644 --- a/x-pack/test/functional/apps/maps/mvt_scaling.js +++ b/x-pack/test/functional/apps/maps/mvt_scaling.js @@ -29,7 +29,7 @@ export default function ({ getPageObjects, getService }) { //Source should be correct expect(mapboxStyle.sources[VECTOR_SOURCE_ID].tiles[0]).to.equal( - '/api/maps/mvt/getTile?x={x}&y={y}&z={z}&geometryFieldName=geometry&index=geo_shapes*&requestBody=(_source:(includes:!(geometry,prop1)),docvalue_fields:!(prop1),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),script_fields:(),size:10000,stored_fields:!(geometry,prop1))' + '/api/maps/mvt/getTile?x={x}&y={y}&z={z}&geometryFieldName=geometry&index=geo_shapes*&requestBody=(_source:(includes:!(geometry,prop1)),docvalue_fields:!(prop1),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),script_fields:(),size:10000,stored_fields:!(geometry,prop1))&geoFieldType=geo_shape' ); //Should correctly load meta for style-rule (sigma is set to 1, opacity to 1) diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts index b5b0f4c94f262..e0bd88a6ab642 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/outlier_detection_creation.ts @@ -11,7 +11,8 @@ export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); const editedDescription = 'Edited description'; - describe('outlier detection creation', function () { + // https://github.com/elastic/kibana/issues/79777 + describe.skip('outlier detection creation', function () { before(async () => { await esArchiver.loadIfNeeded('ml/ihp_outlier'); await ml.testResources.createIndexPatternIfNeeded('ft_ihp_outlier', '@timestamp'); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 9de7d99c095e5..25a7a57e52413 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -40,8 +40,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return createdAlert; } - // FLAKY: https://github.com/elastic/kibana/issues/77401 - describe.skip('alerts', function () { + async function getAlertsByName(name: string) { + const { + body: { data: alerts }, + } = await supertest.get(`/api/alerts/_find?search=${name}&search_fields=name`).expect(200); + + return alerts; + } + + async function deleteAlerts(alertIds: string[]) { + alertIds.forEach(async (alertId: string) => { + await supertest.delete(`/api/alerts/alert/${alertId}`).set('kbn-xsrf', 'foo').expect(204, ''); + }); + } + + describe('alerts', function () { before(async () => { await pageObjects.common.navigateToApp('triggersActions'); await testSubjects.click('alertsTab'); @@ -104,15 +117,18 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { interval: '1m', }, ]); + + // clean up created alert + const alertsToDelete = await getAlertsByName(alertName); + await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id)); }); it('should display alerts in alphabetical order', async () => { const uniqueKey = generateUniqueKey(); - await createAlert({ name: 'b', tags: [uniqueKey] }); - await createAlert({ name: 'c', tags: [uniqueKey] }); - await createAlert({ name: 'a', tags: [uniqueKey] }); + const a = await createAlert({ name: 'b', tags: [uniqueKey] }); + const b = await createAlert({ name: 'c', tags: [uniqueKey] }); + const c = await createAlert({ name: 'a', tags: [uniqueKey] }); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(uniqueKey); const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); @@ -120,11 +136,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(searchResults[0].name).to.eql('a'); expect(searchResults[1].name).to.eql('b'); expect(searchResults[2].name).to.eql('c'); + + await deleteAlerts([a.id, b.id, c.id]); }); it('should search for alert', async () => { const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); @@ -136,11 +153,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { interval: '1m', }, ]); + await deleteAlerts([createdAlert.id]); }); it('should search for tags', async () => { const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(`${createdAlert.name} foo`); const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); @@ -152,10 +169,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { interval: '1m', }, ]); + await deleteAlerts([createdAlert.id]); }); - it('should display an empty list when search removes all alerts', async () => { - await pageObjects.common.navigateToApp('triggersActions'); + it('should display an empty list when search did not return any alerts', async () => { await pageObjects.triggersActionsUI.searchAlerts(`An Alert That For Sure Doesn't Exist!`); expect(await pageObjects.triggersActionsUI.isAlertsListDisplayed()).to.eql(true); @@ -163,7 +180,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should disable single alert', async () => { const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); @@ -177,11 +193,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const disableSwitchAfterDisable = await testSubjects.find('disableSwitch'); const isChecked = await disableSwitchAfterDisable.getAttribute('aria-checked'); expect(isChecked).to.eql('true'); + await deleteAlerts([createdAlert.id]); }); it('should re-enable single alert', async () => { const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); @@ -201,11 +217,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const disableSwitchAfterReEnable = await testSubjects.find('disableSwitch'); const isChecked = await disableSwitchAfterReEnable.getAttribute('aria-checked'); expect(isChecked).to.eql('false'); + await deleteAlerts([createdAlert.id]); }); it('should mute single alert', async () => { const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); @@ -219,11 +235,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const muteSwitchAfterMute = await testSubjects.find('muteSwitch'); const isChecked = await muteSwitchAfterMute.getAttribute('aria-checked'); expect(isChecked).to.eql('true'); + await deleteAlerts([createdAlert.id]); }); it('should unmute single alert', async () => { const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); @@ -243,13 +259,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const muteSwitchAfterUnmute = await testSubjects.find('muteSwitch'); const isChecked = await muteSwitchAfterUnmute.getAttribute('aria-checked'); expect(isChecked).to.eql('false'); + await deleteAlerts([createdAlert.id]); }); it('should delete single alert', async () => { - await createAlert(); - const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); - await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + const firstAlert = await createAlert(); + const secondAlert = await createAlert(); + await pageObjects.triggersActionsUI.searchAlerts(secondAlert.name); await testSubjects.click('collapsedItemActions'); @@ -258,17 +274,19 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('deleteIdsConfirmation > confirmModalConfirmButton'); await testSubjects.missingOrFail('deleteIdsConfirmation'); - const toastTitle = await pageObjects.common.closeToast(); - expect(toastTitle).to.eql('Deleted 1 alert'); - await pageObjects.common.navigateToApp('triggersActions'); - await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + await retry.try(async () => { + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql('Deleted 1 alert'); + }); + + await pageObjects.triggersActionsUI.searchAlerts(secondAlert.name); const searchResultsAfterDelete = await pageObjects.triggersActionsUI.getAlertsList(); expect(searchResultsAfterDelete.length).to.eql(0); + await deleteAlerts([firstAlert.id]); }); it('should mute all selection', async () => { const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); @@ -287,11 +305,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const muteSwitch = await testSubjects.find('muteSwitch'); const isChecked = await muteSwitch.getAttribute('aria-checked'); expect(isChecked).to.eql('true'); + await deleteAlerts([createdAlert.id]); }); it('should unmute all selection', async () => { const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); @@ -312,11 +330,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const muteSwitch = await testSubjects.find('muteSwitch'); const isChecked = await muteSwitch.getAttribute('aria-checked'); expect(isChecked).to.eql('false'); + await deleteAlerts([createdAlert.id]); }); it('should disable all selection', async () => { const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); @@ -335,11 +353,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const disableSwitch = await testSubjects.find('disableSwitch'); const isChecked = await disableSwitch.getAttribute('aria-checked'); expect(isChecked).to.eql('true'); + await deleteAlerts([createdAlert.id]); }); it('should enable all selection', async () => { const createdAlert = await createAlert(); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); @@ -360,6 +378,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const disableSwitch = await testSubjects.find('disableSwitch'); const isChecked = await disableSwitch.getAttribute('aria-checked'); expect(isChecked).to.eql('false'); + await deleteAlerts([createdAlert.id]); }); it('should delete all selection', async () => { @@ -373,7 +392,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { times(2, () => createAlert({ name: `${namePrefix}-1${count++}` })) ); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(namePrefix); for (const createdAlert of createdAlertsFirstPage) { @@ -392,12 +410,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(toastTitle).to.eql('Deleted 10 alerts'); }); - await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(namePrefix); const searchResultsAfterDelete = await pageObjects.triggersActionsUI.getAlertsList(); expect(searchResultsAfterDelete).to.have.length(2); expect(searchResultsAfterDelete[0].name).to.eql(createdAlertsSecondPage[0].name); expect(searchResultsAfterDelete[1].name).to.eql(createdAlertsSecondPage[1].name); + + await deleteAlerts([createdAlertsSecondPage[0].id, createdAlertsSecondPage[1].id]); }); }); }; diff --git a/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts index 7dfef77f001ab..c6cf72f697aa0 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts @@ -12,6 +12,18 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); describe('ingest_manager_agent_policies', () => { + const createdPolicyIds: string[] = []; + after(async () => { + const deletedPromises = createdPolicyIds.map((agentPolicyId) => + supertest + .post(`/api/fleet/agent_policies/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ agentPolicyId }) + .expect(200) + ); + await Promise.all(deletedPromises); + }); + describe('POST /api/fleet/agent_policies', () => { it('should work with valid values', async () => { await supertest @@ -45,6 +57,27 @@ export default function ({ getService }: FtrProviderContext) { }) .expect(400); }); + + it('should return a 409 if policy already exists with name given', async () => { + const sharedBody = { + name: 'Name 1', + namespace: 'default', + }; + + // first one succeeds + await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send(sharedBody) + .expect(200); + + // second one fails because name exists + await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send(sharedBody) + .expect(409); + }); }); describe('POST /api/fleet/agent_policies/{agentPolicyId}/copy', () => { @@ -110,6 +143,90 @@ export default function ({ getService }: FtrProviderContext) { }) .expect(400); }); + + it('should return a 409 if policy already exists with name given', async () => { + const { + body: { item }, + } = await supertest.get(`/api/fleet/agent_policies/${TEST_POLICY_ID}`).expect(200); + + await supertest + .post(`/api/fleet/agent_policies/${TEST_POLICY_ID}/copy`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: item.name, + }) + .expect(409); + }); + }); + + describe('PUT /api/fleet/agent_policies/{agentPolicyId}', () => { + it('should work with valid values', async () => { + const { + body: { item: originalPolicy }, + } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Initial name', + description: 'Initial description', + namespace: 'default', + }) + .expect(200); + + const { + body: { item: updatedPolicy }, + } = await supertest + .put(`/api/fleet/agent_policies/${originalPolicy.id}`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Updated name', + description: 'Updated description', + namespace: 'default', + }) + .expect(200); + createdPolicyIds.push(updatedPolicy.id); + // eslint-disable-next-line @typescript-eslint/naming-convention + const { id, updated_at, ...newPolicy } = updatedPolicy; + + expect(newPolicy).to.eql({ + name: 'Updated name', + description: 'Updated description', + namespace: 'default', + revision: 2, + updated_by: 'elastic', + package_policies: [], + }); + }); + + it('should return a 409 if policy already exists with name given', async () => { + const sharedBody = { + name: 'Initial name', + description: 'Initial description', + namespace: 'default', + }; + + await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send(sharedBody) + .expect(200); + + const { body } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send(sharedBody) + .expect(409); + + expect(body.message).to.match(/already exists?/); + + // same name, different namespace + sharedBody.namespace = 'different'; + await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send(sharedBody) + .expect(200); + }); }); }); } diff --git a/x-pack/test/ingest_manager_api_integration/apis/package_policy/create.ts b/x-pack/test/ingest_manager_api_integration/apis/package_policy/create.ts index ebe42411da038..23fa823c60ed7 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/package_policy/create.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/package_policy/create.ts @@ -31,6 +31,13 @@ export default function ({ getService }: FtrProviderContext) { agentPolicyId = agentPolicyResponse.item.id; }); + after(async function () { + await supertest + .post(`/api/fleet/agent_policies/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ agentPolicyId }); + }); + it('should work with valid values', async function () { if (server.enabled) { await supertest diff --git a/x-pack/test/ingest_manager_api_integration/apis/package_policy/update.ts b/x-pack/test/ingest_manager_api_integration/apis/package_policy/update.ts index c1a5d1f041ce5..8978e70fb6f94 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/package_policy/update.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/package_policy/update.ts @@ -75,6 +75,13 @@ export default function (providerContext: FtrProviderContext) { packagePolicyId2 = packagePolicyResponse2.item.id; }); + after(async function () { + await supertest + .post(`/api/fleet/agent_policies/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ agentPolicyId }); + }); + it('should work with valid values', async function () { await supertest .put(`/api/fleet/package_policies/${packagePolicyId}`) @@ -92,8 +99,7 @@ export default function (providerContext: FtrProviderContext) { title: 'For File Tests', version: '0.1.0', }, - }) - .expect(200); + }); }); it('should return a 500 if there is another package policy with the same name', async function () { diff --git a/x-pack/test/ingest_manager_api_integration/apis/settings/update.ts b/x-pack/test/ingest_manager_api_integration/apis/settings/update.ts index 0156e90b677d1..2c5d154ab0416 100644 --- a/x-pack/test/ingest_manager_api_integration/apis/settings/update.ts +++ b/x-pack/test/ingest_manager_api_integration/apis/settings/update.ts @@ -20,15 +20,28 @@ export default function (providerContext: FtrProviderContext) { skipIfNoDockerRegistry(providerContext); setupIngest(providerContext); + const createdAgentPolicyIds: string[] = []; + after(async () => { + const deletedPromises = createdAgentPolicyIds.map((agentPolicyId) => + supertest + .post(`/api/fleet/agent_policies/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ agentPolicyId }) + .expect(200) + ); + await Promise.all(deletedPromises); + }); it("should bump all agent policy's revision", async function () { const { body: testPolicy1PostRes } = await supertest .post(`/api/fleet/agent_policies`) .set('kbn-xsrf', 'xxxx') .send({ - name: 'test', + name: 'test 1', description: '', namespace: 'default', }); + createdAgentPolicyIds.push(testPolicy1PostRes.item.id); + const { body: testPolicy2PostRes } = await supertest .post(`/api/fleet/agent_policies`) .set('kbn-xsrf', 'xxxx') @@ -37,10 +50,13 @@ export default function (providerContext: FtrProviderContext) { description: '', namespace: 'default', }); + createdAgentPolicyIds.push(testPolicy2PostRes.item.id); + await supertest .put(`/api/fleet/settings`) .set('kbn-xsrf', 'xxxx') - .send({ kibana_urls: ['http://localhost:1232/abc', 'http://localhost:1232/abc'] }); + .send({ kibana_urls: ['http://localhost:1232/abc', 'http://localhost:1232/abc'] }) + .expect(200); const getTestPolicy1Res = await kibanaServer.savedObjects.get({ type: 'ingest-agent-policies', @@ -63,11 +79,13 @@ export default function (providerContext: FtrProviderContext) { description: '', namespace: 'default', }); + createdAgentPolicyIds.push(testPolicyRes.item.id); await supertest .put(`/api/fleet/settings`) .set('kbn-xsrf', 'xxxx') - .send({ kibana_urls: ['http://localhost:1232/abc', 'http://localhost:1232/abc'] }); + .send({ kibana_urls: ['http://localhost:1232/abc', 'http://localhost:1232/abc'] }) + .expect(200); const res = await esClient.search({ index: '.kibana', diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts index 23e717e5c044f..92079c75b1ae9 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts @@ -65,7 +65,7 @@ export const TEST_CASES: Record = Object.freeze({ const createRequest = ({ type, id, initialNamespaces }: BulkCreateTestCase) => ({ type, id, - ...(initialNamespaces && { namespaces: initialNamespaces }), + ...(initialNamespaces && { initialNamespaces }), }); export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: SuperTest) { diff --git a/x-pack/test/saved_object_api_integration/common/suites/create.ts b/x-pack/test/saved_object_api_integration/common/suites/create.ts index 351b0a37a1e59..60f4191a71e93 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/create.ts @@ -127,7 +127,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe const path = `${type}${id ? `/${id}` : ''}`; const requestBody = { attributes: { [NEW_ATTRIBUTE_KEY]: NEW_ATTRIBUTE_VAL }, - ...(initialNamespaces && { namespaces: initialNamespaces }), + ...(initialNamespaces && { initialNamespaces }), }; const query = test.overwrite ? '?overwrite=true' : ''; await supertest