diff --git a/package.json b/package.json index 91034fea5156a..72a63ec34d2c7 100644 --- a/package.json +++ b/package.json @@ -298,7 +298,7 @@ "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^5.0.1", - "@elastic/static-fs": "1.0.1", + "@elastic/static-fs": "1.0.2", "@kbn/dev-utils": "1.0.0", "@kbn/es": "1.0.0", "@kbn/eslint-import-resolver-kibana": "2.0.0", diff --git a/rfcs/text/0011_global_search.md b/rfcs/text/0011_global_search.md new file mode 100644 index 0000000000000..5ec368a1c2f02 --- /dev/null +++ b/rfcs/text/0011_global_search.md @@ -0,0 +1,591 @@ +- Start Date: 2020-04-19 +- RFC PR: [#64284](https://github.com/elastic/kibana/pull/64284) +- Kibana Issue: [#61657](https://github.com/elastic/kibana/issues/61657) + +# Summary + +A new Kibana plugin exposing an API on both public and server side, to allow consumers to search for various objects and +register result providers. + +Note: whether this will be an oss or xpack plugin still depends on https://github.com/elastic/dev/issues/1404. + +# Basic example + +- registering a result provider: + +```ts +setupDeps.globalSearch.registerResultProvider({ + id: 'my_provider', + find: (term, options, context) => { + const resultPromise = myService.search(term, context.core.savedObjects.client); + return from(resultPromise); + }, +}); +``` + +- using the `find` API from the client-side: + +```ts +startDeps.globalSearch.find('some term').subscribe( + ({ results }) => { + updateResults(results); + }, + () => {}, + () => { + showAsyncSearchIndicator(false); + } +); +``` + +# Motivation + +Kibana should do its best to assist users searching for and navigating to the various objects present on the Kibana platform. + +We should expose an API to make it possible for plugins to search for the various objects present on a Kibana instance. + +The first consumer of this API will be the global search bar [#57576](https://github.com/elastic/kibana/issues/57576). This API +should still be generic to answer similar needs from any other consumer, either client or server side. + +# Detailed design + +## API Design + +### Result provider API + +#### common types + +```ts +/** + * Static, non exhaustive list of the common search types. + * Only present to allow consumers and result providers to have aliases to the most common types. + */ +enum GlobalSearchCommonResultTypes { + application = 'application', + dashboard = 'dashboard', + visualization = 'visualization', + search = 'search', +} + +/** + * Options provided to {@link GlobalSearchResultProvider | result providers} `find` method. + */ +interface GlobalSearchProviderFindOptions { + /** + * A custom preference token associated with a search 'session' that should be used to get consistent scoring + * when performing calls to ES. Can also be used as a 'session' token for providers returning data from elsewhere + * than an elasticsearch cluster. + */ + preference: string; + /** + * Observable that emit once if and when the `find` call has been aborted by the consumer, or when the timeout period as been reached. + * When a `find` request is aborted, the service will stop emitting any new result to the consumer anyway, but + * this can (and should) be used to cancel any pending asynchronous task and complete the result observable. + */ + aborted$: Observable; + /** + * The total maximum number of results (including all batches / emissions) that should be returned by the provider for a given `find` request. + * Any result emitted exceeding this quota will be ignored by the service and not emitted to the consumer. + */ + maxResults: number; +} + +/** + * Representation of a result returned by a {@link GlobalSearchResultProvider | result provider} + */ +interface GlobalSearchProviderResult { + /** an id that should be unique for an individual provider's results */ + id: string; + /** the title/label of the result */ + title: string; + /** the type of result */ + type: string; + /** an optional EUI icon name to associate with the search result */ + icon?: string; + /** + * The url associated with this result. + * This can be either an absolute url, a path relative to the basePath, or a structure specifying if the basePath should be prepended. + * + * @example + * `result.url = 'https://kibana-instance:8080/base-path/app/my-app/my-result-type/id';` + * `result.url = '/app/my-app/my-result-type/id';` + * `result.url = { path: '/base-path/app/my-app/my-result-type/id', prependBasePath: false };` + */ + url: string | { path: string; prependBasePath: boolean }; + /** the score of the result, from 1 (lowest) to 100 (highest) */ + score: number; + /** an optional record of metadata for this result */ + meta?: Record; +} +``` + +Notes: + +- The `Serializable` type should be implemented and exposed from `core`. A basic implementation could be: + +```ts +type Serializable = string | number | boolean | PrimitiveArray | PrimitiveRecord; +interface PrimitiveArray extends Array {} +interface PrimitiveRecord extends Record {} +``` + +#### server + +```ts +/** + * Context passed to server-side {@GlobalSearchResultProvider | result provider}'s `find` method. + */ +export interface GlobalSearchProviderContext { + core: { + savedObjects: { + client: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; + }; + elasticsearch: { + legacy: { + client: IScopedClusterClient; + }; + }; + uiSettings: { + client: IUiSettingsClient; + }; + }; +} + +/** + * GlobalSearch result provider, to be registered using the {@link GlobalSearchSetup | global search API} + */ +type GlobalSearchResultProvider = { + id: string; + find( + term: string, + options: GlobalSearchProviderFindOptions, + context: GlobalSearchProviderContext + ): Observable; +}; +``` + +Notes: + +- Initial implementation will only provide a static / non extensible `GlobalSearchProviderContext` context. + It would be possible to allow plugins to register their own context providers as it's done for `RequestHandlerContext`, + but this will not be done until the need arises. +- The performing `request` object could also be exposed on the context to allow result providers + to scope their custom services if needed. However as the previous option, this should only be done once needed. + +#### public + +```ts +/** + * GlobalSearch result provider, to be registered using the {@link GlobalSearchSetup | global search API} + */ +type GlobalSearchResultProvider = { + id: string; + find( + term: string, + options: GlobalSearchProviderFindOptions + ): Observable; +}; +``` + +Notes: + +- The client-side version of `GlobalSearchResultProvider` is slightly different than the + server one, as there is no `context` parameter on the `find` signature. + +### Plugin API + +#### server API + +```ts +/** + * Representation of a result returned by the {@link GlobalSearchPluginStart.find | `find` API} + */ +type GlobalSearchResult = Omit & { + /** + * The url associated with this result. + * This can be either an absolute url, or a relative path including the basePath + */ + url: string; +}; + +/** + * Options for the server-side {@link GlobalSearchServiceStart.find | find API} + */ +interface GlobalSearchFindOptions { + /** + * a custom preference token associated with a search 'session' that should be used to get consistent scoring + * when performing calls to ES. Can also be used as a 'session' token for providers returning data from elsewhere + * than an elasticsearch cluster. + * If not specified, a random token will be generated and used when callingn the underlying result providers. + */ + preference?: string; + /** + * Optional observable to notify that the associated `find` call should be canceled. + * If/when provided and emitting, the result observable will be completed and no further result emission will be performed. + */ + aborted$?: Observable; +} + +/** + * Response returned from the server-side {@link GlobalSearchServiceStart | global search service}'s `find` API + */ +type GlobalSearchBatchedResults = { + /** + * Results for this batch + */ + results: GlobalSearchResult[]; +}; + +/** @public */ +interface GlobalSearchPluginSetup { + registerResultProvider(provider: GlobalSearchResultProvider); +} + +/** @public */ +interface GlobalSearchPluginStart { + find( + term: string, + options: GlobalSearchFindOptions, + request: KibanaRequest + ): Observable; +} +``` + +#### public API + +```ts +/** + * Options for the client-side {@link GlobalSearchServiceStart.find | find API} + */ +interface GlobalSearchFindOptions { + /** + * Optional observable to notify that the associated `find` call should be canceled. + * If/when provided and emitting, the result observable will be completed and no further result emission will be performed. + */ + aborted$?: Observable; +} + +/** + * Enhanced {@link GlobalSearchResult | result type} for the client-side, + * to allow navigating to a given result. + */ +interface NavigableGlobalSearchResult extends GlobalSearchResult { + /** + * Navigate to this result's associated url. If the result is on this kibana instance, user will be redirected to it + * in a SPA friendly way using `application.navigateToApp`, else, a full page refresh will be performed. + */ + navigate: () => Promise; +} + +/** + * Response returned from the client-side {@link GlobalSearchServiceStart | global search service}'s `find` API + */ +type GlobalSearchBatchedResults = { + /** + * Results for this batch + */ + results: NavigableGlobalSearchResult[]; +}; + +/** @public */ +interface GlobalSearchPluginSetup { + registerResultProvider(provider: GlobalSearchResultProvider); +} + +/** @public */ +interface GlobalSearchPluginStart { + find(term: string, options: GlobalSearchFindOptions): Observable; +} +``` + +Notes: + +- The public API is very similar to its server counterpart. The differences are: + - The `registerResultProvider` setup APIs share the same signature, however the input `GlobalSearchResultProvider` + types are different on the client and server. + - The `find` start API signature got a `KibanaRequest` for `server`, when this parameter is not present for `public`. +- The `find` API returns a observable of `NavigableGlobalSearchResult` instead of plain `GlobalSearchResult`. This type + is here to enhance results with a `navigate` method to let the `GlobalSearch` plugin handle the navigation logic, which is + non-trivial. See the [Redirecting to a result](#redirecting-to-a-result) section for more info. + +#### http API + +An internal HTTP API will be exposed on `/internal/global_search/find` to allow the client-side `GlobalSearch` plugin +to fetch results from the server-side result providers. + +It should be very close to: + +```ts +router.post( + { + path: '/internal/global_search/find', + validate: { + body: schema.object({ + term: schema.string(), + options: schema.maybe( + schema.object({ + preference: schema.maybe(schema.string()), + }) + ), + }), + }, + }, + async (ctx, req, res) => { + const { term, options } = req.body; + const results = await ctx.globalSearch + .find(term, { ...options, $aborted: req.events.aborted$ }) + .pipe(reduce((acc, results) => [...acc, ...results])) + .toPromise(); + return res.ok({ + body: { + results, + }, + }); + } +); +``` + +Notes: + +- This API is only for internal use and communication between the client and the server parts of the `GS` API. When + the need to expose an API for external consumers will appear, a new public API will be exposed for that. +- A new `globalSearch` context will be exposed on core's `RequestHandlerContext` to wrap a `find` call with current request. +- Example implementation is awaiting for all results and then returns them as a single response. Ideally, we would + leverage the `bfetch` plugin to stream the results to the client instead. + +## Functional behavior + +### summary + +- the `GlobalSearch` plugin setup contract exposes an API to be able to register result providers (`GlobalSearchResultProvider`). + These providers can be registered from either public or server side, even if the interface for each side is not + exactly the same. +- the `GlobalSearch` plugin start contract exposes an API to be able to search for objects. This API is available from both public + and server sides. + - When using the server `find` API, only results from providers registered from the server will be returned. + - When using the public `find` API, results from provider registered from both server and public sides will be returned. +- During a `find` call, the service will call all the registered result providers and collect their result observables. + Every time a result provider emits some new results, the `globalSearch` service will: + - process them to convert their url to the expected output format + - emit the processed results + +### result provider registration + +Due to the fact that some kind of results (i.e `application`, and maybe later `management_section`) only exists on +the public side of Kibana and therefor are not known on the server side, the `registerResultProvider` API will be +available both from the public and the server counterpart of the `GlobalSearchPluginSetup` contract. + +However, as results from providers registered from the client-side will not be available from the server's `find` API, +registering result providers from the client should only be done to answer this specific use case and will be +discouraged, by providing appropriated jsdoc and documentation explaining that it should only +be used when it is not technically possible to register it from the server side instead. + +### results url processing + +When retrieving results from providers, the GS service will convert them from the provider's `GlobalSearchProviderResult` +result type to `GlobalSeachResult`, which is the structure returned from the `GlobalSearchPluginStart.find` observable. + +In current specification, the only conversion step is to transform the `result.url` property following this logic: + +- if `url` is an absolute url, it will not be modified +- if `url` is a relative path, the basePath will be prepended using `basePath.prepend` +- if `url` is a `{ path: string; prependBasePath: boolean }` structure: + - if `prependBasePath` is true, the basePath will be prepended to the given `path` using `basePath.prepend` + - if `prependBasePath` is false, the given `path` will be returned unmodified + +#### redirecting to a result + +Parsing a relative or absolute result url to perform SPA navigation can be non trivial, and should remains the responsibility +of the GlobalSearch plugin API. + +This is why `NavigableGlobalSearchResult.navigate` has been introduced on the client-side version of the `find` API + +When using `navigate` from a result instance, the following logic will be executed: + +If all these criteria are true for `result.url`: + +- (only for absolute URLs) The origin of the URL matches the origin of the browser's current location +- The pathname of the URL starts with the current basePath (eg. /mybasepath/s/my-space) +- The pathname segment after the basePath matches any known application route (eg. /app// or any application's `appRoute` configuration) + +Then: match the pathname segment to the corresponding application and do the SPA navigation to that application using +`application.navigateToApp` using the remaining pathname segment for the `path` option. + +Otherwise: do a full page navigation using `window.location.assign` + +### searching from the server side + +When calling `GlobalSearchPluginStart.find` from the server-side service: + +- the service will call `find` on each server-side registered result provider and collect the resulting result observables + +- then, the service will merge every result observable and trigger the next step on every emission until either + - A predefined timeout duration is reached + - All result observables are completed + +- on every emission of the merged observable, the results will be processed then emitted. + +A very naive implementation of this behavior would be: + +```ts +search( + term: string, + options: GlobalSearchFindOptions, + request: KibanaRequest +): Observable { + const aborted$ = merge(timeout$, options.$aborted).pipe(first()) + const fromProviders$ = this.providers.map(p => + p.find(term, { ...options, aborted$ }, contextFromRequest(request)) + ); + return merge([...fromProviders$]).pipe( + takeUntil(aborted$), + map(newResults => { + return process(newResults); + }), + ); +} +``` + +### searching from the client side + +When calling `GlobalSearchPluginStart.find` from the public-side service: + +- The service will call: + + - the server-side API via an http call to fetch results from the server-side result providers + - `find` on each client-side registered result provider and collect the resulting observables + +- Then, the service will merge every result observable and trigger the next step on every emission until either + + - A predefined timeout duration is reached + - All result observables are completed + +- on every emission of the merged observable, the results will be processed then emitted. + +A very naive implementation of this behavior would be: + +``` +search( + term: string, + options: GlobalSearchFindOptions, +): Observable { + const aborted$ = merge(timeout$, options.$aborted).pipe(first()) + const fromProviders$ = this.providers.map(p => + p.find(term, { ...options, aborted$ }) + ); + const fromServer$ = of(this.fetchServerResults(term, options, aborted$)) + return merge([...fromProviders$, fromServer$]).pipe( + takeUntil(aborted$), + map(newResults => { + return process(newResults); + }), + ); +} +``` + +Notes: + +- The example implementation is not streaming results from the server, meaning that all results from server-side + registered providers will all be fetched and emitted in a single batch. Ideally, we would leverage the `bfetch` plugin + to stream the results to the client instead. + +### results sorting + +As the GS `find` API is 'streaming' the results from the result providers by emitting the results in batches, sorting results in +each individual batch, even if technically possible, wouldn't provide much value as the consumer will need to sort the +aggregated results on each emission anyway. This is why the results emitted by the `find` API should be considered as +unsorted. Consumers should implement sorting themselves, using either the `score` attribute, or any other arbitrary logic. + +#### Note on score value + +Due to the fact that the results will be coming from various providers, from multiple ES queries or even not from ES, +using a centralized scoring mechanism is not possible. + +the `GlobalSearchResult` contains a `score` field, with an expected value going from 1 (lowest) to 100 (highest). +How this field is populated from each individual provider is considered an implementation detail. + +### Search cancellation + +Consumers can cancel a `find` call at any time by providing a cancellation observable with +the `GlobalSearchFindOptions.aborted$` option and then emitting from it. + +When this observable is provided and emitting, the GS service will complete the result observable. + +This observable will also be passed down to the underlying result providers, that can leverage it to cancel any pending +asynchronous task and perform cleanup if necessary. + +# Drawbacks + +See alternatives. + +# Alternatives + +## Result providers could be only registrable from the server-side API + +The fact that some kinds of results, and therefore some result providers, must be on the client-side makes the API more complex, +while making these results not available from the server-side and HTTP APIs. + +We could decide to only allow providers registration from the server-side. It would reduce API exposure, while simplifying +the service implementation. However to do that, we would need to find a solution to be able to implement a server-side +result provider for `application` (and later `management_section`) type provider. + +I will directly exclude the option to move the `application` registration (`core.application.register`) from client +to server-side, as it's a very heavy impacting (and breaking) change to `core` APIs that would requires more reasons +than just this RFC/API to consider. + +### AST parsing + +One option to make the `application` results 'visible' from the server-side would be to parse the client code at build time +using AST to find all usages to `application.register` inspect the parameters, and generates a server file +containing the applications. The server-side `application` result provider would then just read this file and uses it +to return application results. + +However + +- At the parsing would be done at build time, we would not be able to generate entries for any 3rd party plugins +- As entries for every existing applications would be generated, the search provider would to be able to know which + applications are actually enabled/accessible at runtime to filter them, which is all but easy +- It will also not contains test plugin apps, making it really hard to FTR +- AST parsing is a complex mechanism for an already unsatisfactory alternative + +### Duplicated server-side `application.register` API + +One other option would be to duplicate the `application.register` API on the server side, with a subset of the +client-side metadata. + +```ts +core.application.register({ + id: 'app_status', + title: 'App Status', + euiIconType: 'snowflake', +}); +``` + +This way, the applications could be searchable from the server using this server-side `applications` registry. + +However + +- It forces plugin developers to add this API call. In addition to be a very poor developer experience, it can also + very easily be forgotten, making a given app non searchable +- client-side only plugins would need to add a server-side part to their plugin just to register their application on + the server side + +# Adoption strategy + +The `globalSearch` service is a new feature provided by the `core` API. Also, the base providers +used to search for saved objects and applications will be implemented by the platform team, meaning +that by default, plugin developers won't have to do anything. + +Plugins that wish to expose additional result providers will easily be able to do so by using the exposed APIs and +documentation. + +# How we teach this + +This follows the same patterns we have used for other Core APIs: Observables subscriptions, etc. + +This should be taught using the same channels we've leveraged for other Kibana Platform APIs, API documentation and +example plugins. + +# Unresolved questions + +N/A diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx index aad7e6dcf270a..4317ede547202 100644 --- a/src/core/public/application/ui/app_container.tsx +++ b/src/core/public/application/ui/app_container.tsx @@ -87,6 +87,8 @@ export const AppContainer: FunctionComponent = ({ })) || null; } catch (e) { // TODO: add error UI + // eslint-disable-next-line no-console + console.error(e); } finally { setShowSpinner(false); setIsMounting(false); diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index 4ce422e1f65c4..0ca87eae6e235 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -62,6 +62,7 @@ configService.atPath.mockReturnValue( disableProtection: true, whitelist: [], }, + customResponseHeaders: {}, } as any) ); diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 7ac707b0f3d83..0976bfd56682e 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -18,7 +18,8 @@ */ import uuid from 'uuid'; -import { config } from '.'; +import { config, HttpConfig } from './http_config'; +import { CspConfig } from '../csp'; const validHostnames = ['www.example.com', '8.8.8.8', '::1', 'localhost']; const invalidHostname = 'asdf$%^'; @@ -107,6 +108,23 @@ test('throws if xsrf.whitelist element does not start with a slash', () => { ); }); +test('accepts any type of objects for custom headers', () => { + const httpSchema = config.schema; + const obj = { + customResponseHeaders: { + string: 'string', + bool: true, + number: 12, + array: [1, 2, 3], + nested: { + foo: 1, + bar: 'dolly', + }, + }, + }; + expect(() => httpSchema.validate(obj)).not.toThrow(); +}); + describe('with TLS', () => { test('throws if TLS is enabled but `redirectHttpFromPort` is equal to `port`', () => { const httpSchema = config.schema; @@ -173,3 +191,30 @@ describe('with compression', () => { expect(() => httpSchema.validate(obj)).toThrowErrorMatchingSnapshot(); }); }); + +describe('HttpConfig', () => { + it('converts customResponseHeaders to strings or arrays of strings', () => { + const httpSchema = config.schema; + const rawConfig = httpSchema.validate({ + customResponseHeaders: { + string: 'string', + bool: true, + number: 12, + array: [1, 2, 3], + nested: { + foo: 1, + bar: 'dolly', + }, + }, + }); + const httpConfig = new HttpConfig(rawConfig, CspConfig.DEFAULT); + + expect(httpConfig.customResponseHeaders).toEqual({ + string: 'string', + bool: 'true', + number: '12', + array: ['1', '2', '3'], + nested: '{"foo":1,"bar":"dolly"}', + }); + }); +}); diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 73f44f3c5ab5c..7c72e3270743e 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -57,7 +57,7 @@ export const config = { ), schema.boolean({ defaultValue: false }) ), - customResponseHeaders: schema.recordOf(schema.string(), schema.string(), { + customResponseHeaders: schema.recordOf(schema.string(), schema.any(), { defaultValue: {}, }), host: schema.string({ @@ -136,7 +136,7 @@ export class HttpConfig { public socketTimeout: number; public port: number; public cors: boolean | { origin: string[] }; - public customResponseHeaders: Record; + public customResponseHeaders: Record; public maxPayload: ByteSizeValue; public basePath?: string; public rewriteBasePath: boolean; @@ -153,7 +153,15 @@ export class HttpConfig { this.host = rawHttpConfig.host; this.port = rawHttpConfig.port; this.cors = rawHttpConfig.cors; - this.customResponseHeaders = rawHttpConfig.customResponseHeaders; + this.customResponseHeaders = Object.entries(rawHttpConfig.customResponseHeaders ?? {}).reduce( + (headers, [key, value]) => { + return { + ...headers, + [key]: Array.isArray(value) ? value.map(e => convertHeader(e)) : convertHeader(value), + }; + }, + {} + ); this.maxPayload = rawHttpConfig.maxPayload; this.name = rawHttpConfig.name; this.basePath = rawHttpConfig.basePath; @@ -166,3 +174,7 @@ export class HttpConfig { this.xsrf = rawHttpConfig.xsrf; } } + +const convertHeader = (entry: any): string => { + return typeof entry === 'object' ? JSON.stringify(entry) : String(entry); +}; diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index 49c4d690c6876..0e639aa72a825 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -45,6 +45,7 @@ configService.atPath.mockReturnValue( disableProtection: true, whitelist: [], }, + customResponseHeaders: {}, } as any) ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js index a8376c0e84bf9..24bc4ba8fba5b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js @@ -18,6 +18,7 @@ */ import { management } from 'ui/management'; +import { ManagementSectionId } from '../../../../../../../plugins/management/public'; import './create_index_pattern_wizard'; import './edit_index_pattern'; import uiRoutes from 'ui/routes'; @@ -163,7 +164,7 @@ uiModules }; }); -management.getSection('kibana').register('index_patterns', { +management.getSection(ManagementSectionId.Kibana).register('index_patterns', { display: i18n.translate('kbn.management.indexPattern.sectionsHeader', { defaultMessage: 'Index Patterns', }), diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts index 04eeff1e1f3ce..2784b74ab726c 100644 --- a/src/plugins/advanced_settings/public/plugin.ts +++ b/src/plugins/advanced_settings/public/plugin.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { ManagementApp } from '../../management/public'; +import { ManagementApp, ManagementSectionId } from '../../management/public'; import { ComponentRegistry } from './component_registry'; import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types'; @@ -32,15 +32,12 @@ export class AdvancedSettingsPlugin implements Plugin { private managementApp?: ManagementApp; public setup(core: CoreSetup, { management }: AdvancedSettingsPluginSetup) { - const kibanaSection = management.sections.getSection('kibana'); - if (!kibanaSection) { - throw new Error('`kibana` management section not found.'); - } + const kibanaSection = management.sections.getSection(ManagementSectionId.Kibana); this.managementApp = kibanaSection.registerApp({ id: 'settings', title, - order: 20, + order: 3, async mount(params) { const { mountManagementSection } = await import( './management_app/mount_management_section' diff --git a/src/plugins/data/public/search/long_query_notification.tsx b/src/plugins/data/public/search/long_query_notification.tsx index 590fee20db690..0bdf8ab7c66f8 100644 --- a/src/plugins/data/public/search/long_query_notification.tsx +++ b/src/plugins/data/public/search/long_query_notification.tsx @@ -44,9 +44,7 @@ export function LongQueryNotification(props: Props) { { - await props.application.navigateToApp( - 'kibana#/management/elasticsearch/license_management' - ); + await props.application.navigateToApp('kibana#/management/stack/license_management'); }} > { + this.main.register(id, { + display: title, + order: idx, + }); }); return this.main; diff --git a/src/plugins/management/public/management_section.test.ts b/src/plugins/management/public/management_section.test.ts index c68175ee0a678..e1d047425ac18 100644 --- a/src/plugins/management/public/management_section.test.ts +++ b/src/plugins/management/public/management_section.test.ts @@ -18,6 +18,7 @@ */ import { ManagementSection } from './management_section'; +import { ManagementSectionId } from './types'; // @ts-ignore import { LegacyManagementSection } from './legacy'; import { coreMock } from '../../../core/public/mocks'; @@ -27,7 +28,7 @@ function createSection(registerLegacyApp: () => void) { const getLegacySection = () => legacySection; const getManagementSections: () => ManagementSection[] = () => []; - const testSectionConfig = { id: 'test-section', title: 'Test Section' }; + const testSectionConfig = { id: ManagementSectionId.Data, title: 'Test Section' }; return new ManagementSection( testSectionConfig, getManagementSections, diff --git a/src/plugins/management/public/management_section.ts b/src/plugins/management/public/management_section.ts index 483605341ae4c..ace8f87bec766 100644 --- a/src/plugins/management/public/management_section.ts +++ b/src/plugins/management/public/management_section.ts @@ -17,7 +17,9 @@ * under the License. */ -import { CreateSection, RegisterManagementAppArgs } from './types'; +import { ReactElement } from 'react'; + +import { CreateSection, RegisterManagementAppArgs, ManagementSectionId } from './types'; import { KibanaLegacySetup } from '../../kibana_legacy/public'; import { StartServicesAccessor } from '../../../core/public'; // @ts-ignore @@ -25,8 +27,8 @@ import { LegacyManagementSection } from './legacy'; import { ManagementApp } from './management_app'; export class ManagementSection { - public readonly id: string = ''; - public readonly title: string = ''; + public readonly id: ManagementSectionId; + public readonly title: string | ReactElement = ''; public readonly apps: ManagementApp[] = []; public readonly order: number; public readonly euiIconType?: string; diff --git a/src/plugins/management/public/management_sections.tsx b/src/plugins/management/public/management_sections.tsx new file mode 100644 index 0000000000000..77e494626a00e --- /dev/null +++ b/src/plugins/management/public/management_sections.tsx @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiIcon } from '@elastic/eui'; + +import { ManagementSectionId } from './types'; + +interface Props { + text: string; + tip: string; +} + +const ManagementSectionTitle = ({ text, tip }: Props) => ( + + + {text} + + + + + + +); + +export const managementSections = [ + { + id: ManagementSectionId.Ingest, + title: ( + + ), + }, + { + id: ManagementSectionId.Data, + title: , + }, + { + id: ManagementSectionId.InsightsAndAlerting, + title: ( + + ), + }, + { + id: ManagementSectionId.Security, + title: , + }, + { + id: ManagementSectionId.Kibana, + title: , + }, + { + id: ManagementSectionId.Stack, + title: , + }, +]; diff --git a/src/plugins/management/public/management_service.test.ts b/src/plugins/management/public/management_service.test.ts index 18569ef285ff3..1507d6f43619d 100644 --- a/src/plugins/management/public/management_service.test.ts +++ b/src/plugins/management/public/management_service.test.ts @@ -18,6 +18,7 @@ */ import { ManagementService } from './management_service'; +import { ManagementSectionId } from './types'; import { coreMock } from '../../../core/public/mocks'; import { npSetup } from '../../../legacy/ui/public/new_platform/__mocks__'; @@ -29,27 +30,11 @@ test('Provides default sections', () => { () => {}, coreMock.createSetup().getStartServices ); - expect(service.getAllSections().length).toEqual(2); - expect(service.getSection('kibana')).not.toBeUndefined(); - expect(service.getSection('elasticsearch')).not.toBeUndefined(); -}); - -test('Register section, enable and disable', () => { - const service = new ManagementService().setup( - npSetup.plugins.kibanaLegacy, - () => {}, - coreMock.createSetup().getStartServices - ); - const testSection = service.register({ id: 'test-section', title: 'Test Section' }); - expect(service.getSection('test-section')).not.toBeUndefined(); - - const testApp = testSection.registerApp({ - id: 'test-app', - title: 'Test App', - mount: () => () => {}, - }); - expect(testSection.getApp('test-app')).not.toBeUndefined(); - expect(service.getSectionsEnabled().length).toEqual(1); - testApp.disable(); - expect(service.getSectionsEnabled().length).toEqual(0); + expect(service.getAllSections().length).toEqual(6); + expect(service.getSection(ManagementSectionId.Ingest)).toBeDefined(); + expect(service.getSection(ManagementSectionId.Data)).toBeDefined(); + expect(service.getSection(ManagementSectionId.InsightsAndAlerting)).toBeDefined(); + expect(service.getSection(ManagementSectionId.Security)).toBeDefined(); + expect(service.getSection(ManagementSectionId.Kibana)).toBeDefined(); + expect(service.getSection(ManagementSectionId.Stack)).toBeDefined(); }); diff --git a/src/plugins/management/public/management_service.ts b/src/plugins/management/public/management_service.ts index 8fc207e32e6ce..85d27a526d402 100644 --- a/src/plugins/management/public/management_service.ts +++ b/src/plugins/management/public/management_service.ts @@ -17,11 +17,14 @@ * under the License. */ +import { ReactElement } from 'react'; + import { ManagementSection } from './management_section'; +import { managementSections } from './management_sections'; import { KibanaLegacySetup } from '../../kibana_legacy/public'; // @ts-ignore -import { LegacyManagementSection } from './legacy'; -import { CreateSection } from './types'; +import { LegacyManagementSection, sections } from './legacy'; +import { CreateSection, ManagementSectionId } from './types'; import { StartServicesAccessor, CoreStart } from '../../../core/public'; export class ManagementService { @@ -48,7 +51,8 @@ export class ManagementService { return newSection; }; } - private getSection(sectionId: ManagementSection['id']) { + + private getSection(sectionId: ManagementSectionId) { return this.sections.find(section => section.id === sectionId); } @@ -63,7 +67,13 @@ export class ManagementService { } private sharedInterface = { - getSection: this.getSection.bind(this), + getSection: (sectionId: ManagementSectionId) => { + const section = this.getSection(sectionId); + if (!section) { + throw new Error(`Management section with id ${sectionId} is undefined`); + } + return section; + }, getSectionsEnabled: this.getSectionsEnabled.bind(this), getAllSections: this.getAllSections.bind(this), }; @@ -79,16 +89,13 @@ export class ManagementService { getStartServices ); - register({ id: 'kibana', title: 'Kibana', order: 30, euiIconType: 'logoKibana' }); - register({ - id: 'elasticsearch', - title: 'Elasticsearch', - order: 20, - euiIconType: 'logoElasticsearch', - }); + managementSections.forEach( + ({ id, title }: { id: ManagementSectionId; title: ReactElement }, idx: number) => { + register({ id, title, order: idx }); + } + ); return { - register, ...this.sharedInterface, }; } diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index 82789d3c3f55f..3e32ff4fe26b2 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -30,7 +30,6 @@ const createManagementSectionMock = (): jest.Mocked => ({ sections: { - register: jest.fn(), getSection: jest.fn().mockReturnValue(createManagementSectionMock()), getAllSections: jest.fn().mockReturnValue([]), }, diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index a8bdd5cca24a3..ecd727e8703ff 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -17,6 +17,26 @@ * under the License. */ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the 'License'); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ReactElement } from 'react'; import { IconType } from '@elastic/eui'; import { ManagementApp } from './management_app'; import { ManagementSection } from './management_section'; @@ -31,21 +51,29 @@ export interface ManagementStart { legacy: any; } +export enum ManagementSectionId { + Ingest = 'ingest', + Data = 'data', + InsightsAndAlerting = 'insightsAndAlerting', + Security = 'security', + Kibana = 'kibana', + Stack = 'stack', +} + interface SectionsServiceSetup { - getSection: (sectionId: ManagementSection['id']) => ManagementSection | undefined; + getSection: (sectionId: ManagementSectionId) => ManagementSection; getAllSections: () => ManagementSection[]; - register: RegisterSection; } interface SectionsServiceStart { - getSection: (sectionId: ManagementSection['id']) => ManagementSection | undefined; + getSection: (sectionId: ManagementSectionId) => ManagementSection; getAllSections: () => ManagementSection[]; navigateToApp: ApplicationStart['navigateToApp']; } export interface CreateSection { - id: string; - title: string; + id: ManagementSectionId; + title: string | ReactElement; order?: number; euiIconType?: string; // takes precedence over `icon` property. icon?: string; // URL to image file; fallback if no `euiIconType` diff --git a/src/plugins/saved_objects_management/public/lib/create_field_list.test.ts b/src/plugins/saved_objects_management/public/lib/create_field_list.test.ts index e7d6754ac4d05..7c447aefbc69a 100644 --- a/src/plugins/saved_objects_management/public/lib/create_field_list.test.ts +++ b/src/plugins/saved_objects_management/public/lib/create_field_list.test.ts @@ -59,6 +59,11 @@ describe('createFieldList', () => { "type": "boolean", "value": true, }, + Object { + "name": "references", + "type": "array", + "value": "[]", + }, ] `); }); @@ -76,6 +81,11 @@ describe('createFieldList', () => { \\"data\\": \\"value\\" }", }, + Object { + "name": "references", + "type": "array", + "value": "[]", + }, ] `); }); @@ -93,6 +103,48 @@ describe('createFieldList', () => { 1, 2, 3 + ]", + }, + Object { + "name": "references", + "type": "array", + "value": "[]", + }, + ] + `); + }); + + it(`generates a field for the object's references`, () => { + const obj = createObject( + { + someString: 'foo', + }, + [ + { id: 'ref1', type: 'type', name: 'Ref 1' }, + { id: 'ref12', type: 'other-type', name: 'Ref 2' }, + ] + ); + expect(createFieldList(obj)).toMatchInlineSnapshot(` + Array [ + Object { + "name": "someString", + "type": "text", + "value": "foo", + }, + Object { + "name": "references", + "type": "array", + "value": "[ + { + \\"id\\": \\"ref1\\", + \\"type\\": \\"type\\", + \\"name\\": \\"Ref 1\\" + }, + { + \\"id\\": \\"ref12\\", + \\"type\\": \\"other-type\\", + \\"name\\": \\"Ref 2\\" + } ]", }, ] @@ -126,6 +178,11 @@ describe('createFieldList', () => { "type": "text", "value": "B", }, + Object { + "name": "references", + "type": "array", + "value": "[]", + }, ] `); }); diff --git a/src/plugins/saved_objects_management/public/lib/create_field_list.ts b/src/plugins/saved_objects_management/public/lib/create_field_list.ts index 5d87c11a87198..d66d0b0a28844 100644 --- a/src/plugins/saved_objects_management/public/lib/create_field_list.ts +++ b/src/plugins/saved_objects_management/public/lib/create_field_list.ts @@ -29,12 +29,15 @@ export function createFieldList( object: SimpleSavedObject, service?: SavedObjectLoader ): ObjectField[] { - const fields = Object.entries(object.attributes as Record).reduce( + let fields = Object.entries(object.attributes as Record).reduce( (objFields, [key, value]) => { - return [...objFields, ...recursiveCreateFields(key, value)]; + return [...objFields, ...createFields(key, value)]; }, [] as ObjectField[] ); + // Special handling for references which isn't within "attributes" + fields = [...fields, ...createFields('references', object.references)]; + if (service && (service as any).Class) { addFieldsFromClass((service as any).Class, fields); } @@ -53,7 +56,7 @@ export function createFieldList( * @param {array} parents The parent keys to the field * @returns {array} */ -const recursiveCreateFields = (key: string, value: any, parents: string[] = []): ObjectField[] => { +const createFields = (key: string, value: any, parents: string[] = []): ObjectField[] => { const path = [...parents, key]; if (path.length > maxRecursiveIterations) { return []; @@ -78,7 +81,7 @@ const recursiveCreateFields = (key: string, value: any, parents: string[] = []): } else if (isPlainObject(field.value)) { let fields: ObjectField[] = []; forOwn(field.value, (childValue, childKey) => { - fields = [...fields, ...recursiveCreateFields(childKey as string, childValue, path)]; + fields = [...fields, ...createFields(childKey as string, childValue, path)]; }); return fields; } diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx index 1b69eb4240d68..afed6b492dc91 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx @@ -120,6 +120,7 @@ export class Field extends PureComponent { return (
{ set(source, field.name, value); }); + // we extract the `references` field that does not belong to attributes const { references, ...attributes } = source; await onSave({ attributes, references }); diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index 28eac96dcbf46..b0c6b1952a2a5 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { ManagementSetup } from '../../management/public'; +import { ManagementSetup, ManagementSectionId } from '../../management/public'; import { DataPublicPluginStart } from '../../data/public'; import { DashboardStart } from '../../dashboard/public'; import { DiscoverStart } from '../../discover/public'; @@ -87,16 +87,13 @@ export class SavedObjectsManagementPlugin category: FeatureCatalogueCategory.ADMIN, }); - const kibanaSection = management.sections.getSection('kibana'); - if (!kibanaSection) { - throw new Error('`kibana` management section not found.'); - } + const kibanaSection = management.sections.getSection(ManagementSectionId.Kibana); kibanaSection.registerApp({ id: 'objects', title: i18n.translate('savedObjectsManagement.managementSectionLabel', { defaultMessage: 'Saved Objects', }), - order: 10, + order: 1, mount: async mountParams => { const { mountManagementSection } = await import('./management_section'); return mountManagementSection({ diff --git a/test/functional/apps/dashboard/dashboard_clone.js b/test/functional/apps/dashboard/dashboard_clone.js index 8b7f6ba6a34dd..5d49e0cb97088 100644 --- a/test/functional/apps/dashboard/dashboard_clone.js +++ b/test/functional/apps/dashboard/dashboard_clone.js @@ -42,12 +42,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.clickClone(); await PageObjects.dashboard.confirmClone(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - clonedDashboardName - ); - - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', clonedDashboardName, 1); }); it('the copy should have all the same visualizations', async function() { @@ -75,11 +70,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.cancelClone(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 1); }); it('Clones on confirm duplicate title warning', async function() { @@ -92,11 +83,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.waitForRenderComplete(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName + ' Copy' - ); - expect(countOfDashboards).to.equal(2); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName + ' Copy', 2); }); }); } diff --git a/test/functional/apps/dashboard/dashboard_listing.js b/test/functional/apps/dashboard/dashboard_listing.js index e3e835109da2c..2ab2e107dae2d 100644 --- a/test/functional/apps/dashboard/dashboard_listing.js +++ b/test/functional/apps/dashboard/dashboard_listing.js @@ -42,11 +42,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.saveDashboard(dashboardName); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 1); }); it('is not shown when there is a dashboard', async function() { @@ -55,11 +51,7 @@ export default function({ getService, getPageObjects }) { }); it('is not shown when there are no dashboards shown during a search', async function() { - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - 'gobeldeguck' - ); - expect(countOfDashboards).to.equal(0); + await listingTable.searchAndExpectItemsCount('dashboard', 'gobeldeguck', 0); const promptExists = await PageObjects.dashboard.getCreateDashboardPromptExists(); expect(promptExists).to.be(false); @@ -78,11 +70,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.common.expectConfirmModalOpenState(false); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 1); }); it('succeeds on confirmation press', async function() { @@ -91,11 +79,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.common.clickConfirmOnModal(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(0); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 0); }); }); @@ -109,38 +93,32 @@ export default function({ getService, getPageObjects }) { it('matches on the first word', async function() { await listingTable.searchForItemWithName('Two'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(1); + await listingTable.expectItemsCount('dashboard', 1); }); it('matches the second word', async function() { await listingTable.searchForItemWithName('Words'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(1); + await listingTable.expectItemsCount('dashboard', 1); }); it('matches the second word prefix', async function() { await listingTable.searchForItemWithName('Wor'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(1); + await listingTable.expectItemsCount('dashboard', 1); }); it('does not match mid word', async function() { await listingTable.searchForItemWithName('ords'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(0); + await listingTable.expectItemsCount('dashboard', 0); }); it('is case insensitive', async function() { await listingTable.searchForItemWithName('two words'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(1); + await listingTable.expectItemsCount('dashboard', 1); }); it('is using AND operator', async function() { await listingTable.searchForItemWithName('three words'); - const countOfDashboards = await listingTable.getItemsCount('dashboard'); - expect(countOfDashboards).to.equal(0); + await listingTable.expectItemsCount('dashboard', 0); }); }); diff --git a/test/functional/apps/dashboard/dashboard_save.js b/test/functional/apps/dashboard/dashboard_save.js index 7ffe951faa398..d0da033788689 100644 --- a/test/functional/apps/dashboard/dashboard_save.js +++ b/test/functional/apps/dashboard/dashboard_save.js @@ -17,8 +17,6 @@ * under the License. */ -import expect from '@kbn/expect'; - export default function({ getPageObjects, getService }) { const PageObjects = getPageObjects(['dashboard', 'header']); const listingTable = getService('listingTable'); @@ -50,11 +48,7 @@ export default function({ getPageObjects, getService }) { await PageObjects.dashboard.cancelSave(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 1); }); it('Saves on confirm duplicate title warning', async function() { @@ -73,11 +67,7 @@ export default function({ getPageObjects, getService }) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardName - ); - expect(countOfDashboards).to.equal(2); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardName, 2); }); it('Does not warn when you save an existing dashboard with the title it already has, and that title is a duplicate', async function() { @@ -128,11 +118,7 @@ export default function({ getPageObjects, getService }) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await listingTable.searchAndGetItemsCount( - 'dashboard', - dashboardNameEnterKey - ); - expect(countOfDashboards).to.equal(1); + await listingTable.searchAndExpectItemsCount('dashboard', dashboardNameEnterKey, 1); }); }); } diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index 6af91ac9c5c94..1a85ff86498dc 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -26,6 +26,8 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'settings']); + const browser = getService('browser'); + const find = getService('find'); const setFieldValue = async (fieldName: string, value: string) => { return testSubjects.setValue(`savedObjects-editField-${fieldName}`, value); @@ -35,6 +37,26 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { return testSubjects.getAttribute(`savedObjects-editField-${fieldName}`, 'value'); }; + const setAceEditorFieldValue = async (fieldName: string, fieldValue: string) => { + const editorId = `savedObjects-editField-${fieldName}-aceEditor`; + await find.clickByCssSelector(`#${editorId}`); + return browser.execute( + (editor: string, value: string) => { + return (window as any).ace.edit(editor).setValue(value); + }, + editorId, + fieldValue + ); + }; + + const getAceEditorFieldValue = async (fieldName: string) => { + const editorId = `savedObjects-editField-${fieldName}-aceEditor`; + await find.clickByCssSelector(`#${editorId}`); + return browser.execute((editor: string) => { + return (window as any).ace.edit(editor).getValue() as string; + }, editorId); + }; + const focusAndClickButton = async (buttonSubject: string) => { const button = await testSubjects.find(buttonSubject); await button.scrollIntoViewIfNecessary(); @@ -99,5 +121,52 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { const objects = await PageObjects.settings.getSavedObjectsInTable(); expect(objects.includes('A Dashboard')).to.be(false); }); + + it('preserves the object references when saving', async () => { + const testVisualizationUrl = + '/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed'; + const visualizationRefs = [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: 'logstash-*', + }, + ]; + + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + + const objects = await PageObjects.settings.getSavedObjectsInTable(); + expect(objects.includes('A Pie')).to.be(true); + + await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl); + + await testSubjects.existOrFail('savedObjectEditSave'); + + let displayedReferencesValue = await getAceEditorFieldValue('references'); + + expect(JSON.parse(displayedReferencesValue)).to.eql(visualizationRefs); + + await focusAndClickButton('savedObjectEditSave'); + + await PageObjects.settings.getSavedObjectsInTable(); + + await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl); + + // Parsing to avoid random keys ordering issues in raw string comparison + expect(JSON.parse(await getAceEditorFieldValue('references'))).to.eql(visualizationRefs); + + await setAceEditorFieldValue('references', JSON.stringify([], undefined, 2)); + + await focusAndClickButton('savedObjectEditSave'); + + await PageObjects.settings.getSavedObjectsInTable(); + + await PageObjects.common.navigateToActualUrl('kibana', testVisualizationUrl); + + displayedReferencesValue = await getAceEditorFieldValue('references'); + + expect(JSON.parse(displayedReferencesValue)).to.eql([]); + }); }); } diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js index 17e0d1ca87fdd..cc25d10cf3257 100644 --- a/test/functional/apps/visualize/_point_series_options.js +++ b/test/functional/apps/visualize/_point_series_options.js @@ -341,6 +341,17 @@ export default function({ getService, getPageObjects }) { log.debug('close inspector'); await inspector.close(); }); + + after(async () => { + const timezone = await kibanaServer.uiSettings.get('dateFormat:tz'); + + // make sure the timezone was set to default correctly to avoid further failures + // for details see https://github.com/elastic/kibana/issues/63037 + if (timezone !== 'UTC') { + log.debug("set 'dateFormat:tz': 'UTC'"); + await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'UTC' }); + } + }); }); }); } diff --git a/test/functional/apps/visualize/_visualize_listing.js b/test/functional/apps/visualize/_visualize_listing.js index e277c3c7d104d..dc07301a8ad50 100644 --- a/test/functional/apps/visualize/_visualize_listing.js +++ b/test/functional/apps/visualize/_visualize_listing.js @@ -17,8 +17,6 @@ * under the License. */ -import expect from '@kbn/expect'; - export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['visualize', 'visEditor']); const listingTable = getService('listingTable'); @@ -37,8 +35,7 @@ export default function({ getService, getPageObjects }) { // type markdown is used for simplicity await PageObjects.visualize.createSimpleMarkdownViz(vizName); await PageObjects.visualize.gotoVisualizationLandingPage(); - const visCount = await listingTable.getItemsCount('visualize'); - expect(visCount).to.equal(1); + await listingTable.expectItemsCount('visualize', 1); }); it('delete all viz', async function() { @@ -46,12 +43,10 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.createSimpleMarkdownViz(vizName + '2'); await PageObjects.visualize.gotoVisualizationLandingPage(); - let visCount = await listingTable.getItemsCount('visualize'); - expect(visCount).to.equal(3); + await listingTable.expectItemsCount('visualize', 3); await PageObjects.visualize.deleteAllVisualizations(); - visCount = await listingTable.getItemsCount('visualize'); - expect(visCount).to.equal(0); + await listingTable.expectItemsCount('visualize', 0); }); }); @@ -69,38 +64,32 @@ export default function({ getService, getPageObjects }) { it('matches on the first word', async function() { await listingTable.searchForItemWithName('Hello'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(1); + await listingTable.expectItemsCount('visualize', 1); }); it('matches the second word', async function() { await listingTable.searchForItemWithName('World'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(1); + await listingTable.expectItemsCount('visualize', 1); }); it('matches the second word prefix', async function() { await listingTable.searchForItemWithName('Wor'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(1); + await listingTable.expectItemsCount('visualize', 1); }); it('does not match mid word', async function() { await listingTable.searchForItemWithName('orld'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(0); + await listingTable.expectItemsCount('visualize', 0); }); it('is case insensitive', async function() { await listingTable.searchForItemWithName('hello world'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(1); + await listingTable.expectItemsCount('visualize', 1); }); it('is using AND operator', async function() { await listingTable.searchForItemWithName('hello banana'); - const itemCount = await listingTable.getItemsCount('visualize'); - expect(itemCount).to.equal(0); + await listingTable.expectItemsCount('visualize', 0); }); }); }); diff --git a/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json index f085bad4c507e..cbaa0306f9a42 100644 --- a/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json +++ b/test/functional/fixtures/es_archiver/saved_objects_management/edit_saved_object/data.json @@ -38,7 +38,14 @@ }, "type": "visualization", "updated_at": "2019-01-22T19:32:31.206Z" - } + }, + "references" : [ + { + "name" : "kibanaSavedObjectMeta.searchSourceJSON.index", + "type" : "index-pattern", + "id" : "logstash-*" + } + ] } } diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts index c7667ae7b4049..9a117458c7f76 100644 --- a/test/functional/services/listing_table.ts +++ b/test/functional/services/listing_table.ts @@ -17,6 +17,7 @@ * under the License. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; export function ListingTableProvider({ getService, getPageObjects }: FtrProviderContext) { @@ -85,11 +86,13 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider * Returns items count on landing page * @param appName 'visualize' | 'dashboard' */ - public async getItemsCount(appName: 'visualize' | 'dashboard'): Promise { - const elements = await find.allByCssSelector( - `[data-test-subj^="${prefixMap[appName]}ListingTitleLink"]` - ); - return elements.length; + public async expectItemsCount(appName: 'visualize' | 'dashboard', count: number) { + await retry.try(async () => { + const elements = await find.allByCssSelector( + `[data-test-subj^="${prefixMap[appName]}ListingTitleLink"]` + ); + expect(elements.length).to.equal(count); + }); } /** @@ -116,12 +119,18 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider * @param appName 'visualize' | 'dashboard' * @param name item name */ - public async searchAndGetItemsCount(appName: 'visualize' | 'dashboard', name: string) { + public async searchAndExpectItemsCount( + appName: 'visualize' | 'dashboard', + name: string, + count: number + ) { await this.searchForItemWithName(name); - const links = await testSubjects.findAll( - `${prefixMap[appName]}ListingTitleLink-${name.replace(/ /g, '-')}` - ); - return links.length; + await retry.try(async () => { + const links = await testSubjects.findAll( + `${prefixMap[appName]}ListingTitleLink-${name.replace(/ /g, '-')}` + ); + expect(links.length).to.equal(count); + }); } public async clickDeleteSelected() { diff --git a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx index f3b7a19f70ae3..96297f6d51566 100644 --- a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx +++ b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx @@ -21,22 +21,17 @@ import * as React from 'react'; import ReactDOM from 'react-dom'; import { HashRouter as Router, Switch, Route, Link } from 'react-router-dom'; import { CoreSetup, Plugin } from 'kibana/public'; -import { ManagementSetup } from '../../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../../src/plugins/management/public'; export class ManagementTestPlugin implements Plugin { public setup(core: CoreSetup, { management }: { management: ManagementSetup }) { - const testSection = management.sections.register({ - id: 'test-section', - title: 'Test Section', - euiIconType: 'logoKibana', - order: 25, - }); + const testSection = management.sections.getSection(ManagementSectionId.Data); - testSection!.registerApp({ + testSection.registerApp({ id: 'test-management', title: 'Management Test', - mount(params) { + mount(params: any) { params.setBreadcrumbs([{ text: 'Management Test' }]); ReactDOM.render( @@ -63,7 +58,7 @@ export class ManagementTestPlugin }, }); - testSection! + testSection .registerApp({ id: 'test-management-disabled', title: 'Management Test Disabled', diff --git a/test/plugin_functional/test_suites/management/management_plugin.js b/test/plugin_functional/test_suites/management/management_plugin.js index 0c185f4b385b5..6ad2bb56391dd 100644 --- a/test/plugin_functional/test_suites/management/management_plugin.js +++ b/test/plugin_functional/test_suites/management/management_plugin.js @@ -40,7 +40,7 @@ export default function({ getService, getPageObjects }) { it('should redirect when app is disabled', async () => { await PageObjects.common.navigateToActualUrl( 'kibana', - 'management/test-section/test-management-disabled' + 'management/data/test-management-disabled' ); await testSubjects.existOrFail('management-landing'); }); diff --git a/x-pack/legacy/plugins/beats_management/common/constants/index.ts b/x-pack/legacy/plugins/beats_management/common/constants/index.ts index 5f9ae815e34d4..8d22b36e96d45 100644 --- a/x-pack/legacy/plugins/beats_management/common/constants/index.ts +++ b/x-pack/legacy/plugins/beats_management/common/constants/index.ts @@ -9,4 +9,4 @@ export { INDEX_NAMES } from './index_names'; export { PLUGIN } from './plugin'; export { LICENSES, REQUIRED_LICENSES, REQUIRED_ROLES } from './security'; export { TABLE_CONFIG } from './table'; -export const BASE_PATH = '/management/beats/beats_management'; +export const BASE_PATH = '/management/ingest/beats_management'; diff --git a/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.js b/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.js index e2a758075155a..b7cd1687dc257 100644 --- a/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.js +++ b/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.js @@ -29,5 +29,9 @@ export function getIndexPatternSettings(indicesSettingsResp) { maxInnerResultWindow = Math.min(indexMaxInnerResultWindow, indexMaxResultWindow); }); - return { maxResultWindow, maxInnerResultWindow }; + return { + maxResultWindow: maxResultWindow === Infinity ? DEFAULT_MAX_RESULT_WINDOW : maxResultWindow, + maxInnerResultWindow: + maxInnerResultWindow === Infinity ? DEFAULT_MAX_INNER_RESULT_WINDOW : maxInnerResultWindow, + }; } diff --git a/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.test.js b/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.test.js index c152f5bfffc31..46949a2e3e2cf 100644 --- a/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.test.js +++ b/x-pack/legacy/plugins/maps/server/lib/get_index_pattern_settings.test.js @@ -24,6 +24,14 @@ describe('max_result_window and max_inner_result_window are not set', () => { expect(maxInnerResultWindow).toBe(DEFAULT_MAX_INNER_RESULT_WINDOW); }); + test('Should provide default values from cross cluster index response', () => { + // _settings returns empty object for cross cluster index + const indicesSettingsResp = {}; + const { maxResultWindow, maxInnerResultWindow } = getIndexPatternSettings(indicesSettingsResp); + expect(maxResultWindow).toBe(DEFAULT_MAX_RESULT_WINDOW); + expect(maxInnerResultWindow).toBe(DEFAULT_MAX_INNER_RESULT_WINDOW); + }); + test('Should include default values when providing minimum values for indices in index pattern', () => { const indicesSettingsResp = { kibana_sample_data_logs: { diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx index 75c6c79bc804a..9001eb6992a96 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/AlertIntegrations/index.tsx @@ -82,7 +82,7 @@ export function AlertIntegrations(props: Props) { } ), href: plugin.core.http.basePath.prepend( - '/app/kibana#/management/kibana/triggersActions/alerts' + '/app/kibana#/management/insightsAndAlerting/triggersActions/alerts' ), icon: 'tableOfContents' } diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx index 3bbd8a01d0549..c0c93bb4cb298 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx @@ -258,7 +258,7 @@ export class WatcherFlyout extends Component< )}{' '} {i18n.translate( 'xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreatedNotificationText.viewWatchLinkText', diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx index 91483b4b52d90..e1f58b7b35210 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx @@ -92,7 +92,7 @@ export class ServiceIntegrations extends React.Component { ), icon: 'watchesApp', href: core.http.basePath.prepend( - '/app/kibana#/management/elasticsearch/watcher' + '/app/kibana#/management/insightsAndAlerting/watcher' ), target: '_blank', onClick: () => this.closePopover() diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx b/x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx index c1afa433cb614..266e5a97ef07a 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/NoServicesMessage.tsx @@ -66,7 +66,7 @@ export function NoServicesMessage({ historicalDataFound, status }: Props) { defaultMessage: 'You may also have old data that needs to be migrated.' })}{' '} - + {i18n.translate('xpack.apm.servicesTable.UpgradeAssistantLink', { defaultMessage: 'Learn more by visiting the Kibana Upgrade Assistant' diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap index 227becb9a9c4f..d027422961c99 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/NoServicesMessage.test.tsx.snap @@ -25,7 +25,7 @@ exports[`NoServicesMessage status: pending and historicalDataFound: false 1`] = You may also have old data that needs to be migrated. Learn more by visiting the Kibana Upgrade Assistant @@ -70,7 +70,7 @@ exports[`NoServicesMessage status: success and historicalDataFound: false 1`] = You may also have old data that needs to be migrated. Learn more by visiting the Kibana Upgrade Assistant diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap index 6d310199ba9a5..3e6be107ce3a1 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap @@ -79,7 +79,7 @@ NodeList [ Learn more by visiting the Kibana Upgrade Assistant diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceOverview/index.tsx index 99b169e3ec361..06f56d9ec1be7 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/index.tsx @@ -66,7 +66,7 @@ export function ServiceOverview() { {i18n.translate( diff --git a/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx b/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx index d2afefb83a568..96e8c754fcc5f 100644 --- a/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/LicensePrompt/index.tsx @@ -17,7 +17,7 @@ interface Props { export const LicensePrompt = ({ text, showBetaBadge = false }: Props) => { const licensePageUrl = useKibanaUrl( '/app/kibana', - '/management/elasticsearch/license_management/home' + '/management/stack/license_management/home' ); const renderLicenseBody = ( diff --git a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx index 36e780f50c3ae..8ed02f039289e 100644 --- a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx +++ b/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx @@ -11,7 +11,7 @@ import { useApmPluginContext } from '../../hooks/useApmPluginContext'; export function InvalidLicenseNotification() { const { core } = useApmPluginContext(); const manageLicenseURL = core.http.basePath.prepend( - '/app/kibana#/management/elasticsearch/license_management' + '/app/kibana#/management/stack/license_management' ); return ( diff --git a/x-pack/plugins/apm/public/setHelpExtension.ts b/x-pack/plugins/apm/public/setHelpExtension.ts index 1a3394651b2ff..aa23a8a2e64ea 100644 --- a/x-pack/plugins/apm/public/setHelpExtension.ts +++ b/x-pack/plugins/apm/public/setHelpExtension.ts @@ -22,7 +22,7 @@ export function setHelpExtension({ chrome, http }: CoreStart) { linkType: 'custom', href: url.format({ pathname: http.basePath.prepend('/app/kibana'), - hash: '/management/elasticsearch/upgrade_assistant' + hash: '/management/stack/upgrade_assistant' }), content: i18n.translate('xpack.apm.helpMenu.upgradeAssistantLink', { defaultMessage: 'Upgrade assistant' diff --git a/x-pack/plugins/beats_management/public/application.tsx b/x-pack/plugins/beats_management/public/application.tsx index bf450e9c7a5e3..6711e93895b62 100644 --- a/x-pack/plugins/beats_management/public/application.tsx +++ b/x-pack/plugins/beats_management/public/application.tsx @@ -19,7 +19,10 @@ import { TagsContainer } from './containers/tags'; import { FrontendLibs } from './lib/types'; import { AppRouter } from './router'; import { services } from './kbn_services'; -import { ManagementAppMountParams } from '../../../../src/plugins/management/public'; +import { + ManagementAppMountParams, + ManagementSectionId, +} from '../../../../src/plugins/management/public'; export const renderApp = ( { basePath, element, setBreadcrumbs }: ManagementAppMountParams, @@ -28,7 +31,7 @@ export const renderApp = ( ReactDOM.render( - + diff --git a/x-pack/plugins/beats_management/public/bootstrap.tsx b/x-pack/plugins/beats_management/public/bootstrap.tsx index ecca9da052fea..9a45be702e212 100644 --- a/x-pack/plugins/beats_management/public/bootstrap.tsx +++ b/x-pack/plugins/beats_management/public/bootstrap.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import { FrontendLibs } from './lib/types'; import { compose } from './lib/compose/kibana'; @@ -20,27 +19,14 @@ async function startApp(libs: FrontendLibs, core: CoreSetup) { await libs.framework.waitUntilFrameworkReady(); if (libs.framework.licenseIsAtLeast('standard')) { - libs.framework.registerManagementSection({ - id: 'beats', - name: i18n.translate('xpack.beatsManagement.centralManagementSectionLabel', { - defaultMessage: 'Beats', - }), - iconName: 'logoBeats', - }); - - libs.framework.registerManagementUI({ - sectionId: 'beats', - appId: 'beats_management', - name: i18n.translate('xpack.beatsManagement.centralManagementLinkLabel', { - defaultMessage: 'Central Management', - }), - async mount(params) { - const [coreStart, pluginsStart] = await core.getStartServices(); - setServices(coreStart, pluginsStart, params); - const { renderApp } = await import('./application'); - return renderApp(params, libs); - }, - }); + const mount = async (params: any) => { + const [coreStart, pluginsStart] = await core.getStartServices(); + setServices(coreStart, pluginsStart, params); + const { renderApp } = await import('./application'); + return renderApp(params, libs); + }; + + libs.framework.registerManagementUI(mount); } } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/adapter_types.ts index 3c0c724d3d87b..9d7a1954b60d9 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/adapter_types.ts @@ -17,19 +17,7 @@ export interface FrameworkAdapter { currentUser: FrameworkUser; // Methods waitUntilFrameworkReady(): Promise; - registerManagementSection(settings: { - id: string; - name: string; - iconName: string; - order?: number; - }): void; - registerManagementUI(settings: { - sectionId: string; - appId: string; - name: string; - order?: number; - mount: RegisterManagementAppArgs['mount']; - }): void; + registerManagementUI(mount: RegisterManagementAppArgs['mount']): void; } export const RuntimeFrameworkInfo = t.type({ diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts index d061e2f35809b..1ae21a561950d 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -9,6 +9,7 @@ import { IScope } from 'angular'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { isLeft } from 'fp-ts/lib/Either'; import { first } from 'rxjs/operators'; +import { i18n } from '@kbn/i18n'; import { SecurityPluginSetup } from '../../../../../security/public'; import { BufferedKibanaServiceCall, KibanaAdapterServiceRefs, KibanaUIConfig } from '../../types'; import { @@ -21,6 +22,7 @@ import { import { ManagementSetup, RegisterManagementAppArgs, + ManagementSectionId, } from '../../../../../../../src/plugins/management/public'; import { LicensingPluginSetup } from '../../../../../licensing/public'; import { BeatsManagementConfigType } from '../../../../common'; @@ -102,40 +104,15 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { } } - public registerManagementSection(settings: { - id: string; - name: string; - iconName: string; - order?: number; - }) { - this.management.sections.register({ - id: settings.id, - title: settings.name, - euiIconType: settings.iconName, - order: settings.order || 30, - }); - } - - public registerManagementUI(settings: { - sectionId: string; - appId: string; - name: string; - order?: number; - mount: RegisterManagementAppArgs['mount']; - }) { - const section = this.management.sections.getSection(settings.sectionId); - - if (!section) { - throw new Error( - `registerManagementUI was called with a sectionId of ${settings.sectionId}, and that is is not yet regestered as a section` - ); - } - + public registerManagementUI(mount: RegisterManagementAppArgs['mount']) { + const section = this.management.sections.getSection(ManagementSectionId.Ingest); section.registerApp({ - id: settings.appId, - title: settings.name, - order: settings.order || 30, - mount: settings.mount, + id: 'beats_management', + title: i18n.translate('xpack.beatsManagement.centralManagementLinkLabel', { + defaultMessage: 'Beats Central Management', + }), + order: 2, + mount, }); } } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/testing_framework_adapter.ts index 56f428ff0b927..52d185e0d7dc9 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/testing_framework_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/testing_framework_adapter.ts @@ -39,15 +39,6 @@ export class TestingFrameworkAdapter implements FrameworkAdapter { return; } - public registerManagementSection(settings: { - id?: string; - name: string; - iconName: string; - order?: number; - }) { - throw new Error('not yet implamented'); - } - public registerManagementUI(settings: { sectionId?: string; name: string; order?: number }) { throw new Error('not yet implamented'); } diff --git a/x-pack/plugins/beats_management/public/lib/framework.ts b/x-pack/plugins/beats_management/public/lib/framework.ts index c850bdd8bc0ee..9e4271c683415 100644 --- a/x-pack/plugins/beats_management/public/lib/framework.ts +++ b/x-pack/plugins/beats_management/public/lib/framework.ts @@ -13,7 +13,6 @@ import { FrameworkAdapter } from './adapters/framework/adapter_types'; export class FrameworkLib { public waitUntilFrameworkReady = this.adapter.waitUntilFrameworkReady.bind(this.adapter); - public registerManagementSection = this.adapter.registerManagementSection.bind(this.adapter); public registerManagementUI = this.adapter.registerManagementUI.bind(this.adapter); constructor(private readonly adapter: FrameworkAdapter) {} diff --git a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts index 797141b0996af..96884cf4bead8 100644 --- a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts +++ b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts @@ -24,8 +24,8 @@ export const APPS = { }; export const MANAGEMENT_ID = 'cross_cluster_replication'; -export const BASE_PATH = `/management/elasticsearch/${MANAGEMENT_ID}`; -export const BASE_PATH_REMOTE_CLUSTERS = '/management/elasticsearch/remote_clusters'; +export const BASE_PATH = `/management/data/${MANAGEMENT_ID}`; +export const BASE_PATH_REMOTE_CLUSTERS = '/management/data/remote_clusters'; export const API_BASE_PATH = '/api/cross_cluster_replication'; export const API_REMOTE_CLUSTERS_BASE_PATH = '/api/remote_clusters'; export const API_INDEX_MANAGEMENT_BASE_PATH = '/api/index_management'; diff --git a/x-pack/plugins/cross_cluster_replication/public/plugin.ts b/x-pack/plugins/cross_cluster_replication/public/plugin.ts index dfe9e4e657c30..561da838a4202 100644 --- a/x-pack/plugins/cross_cluster_replication/public/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/public/plugin.ts @@ -9,6 +9,7 @@ import { get } from 'lodash'; import { first } from 'rxjs/operators'; import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; +import { ManagementSectionId } from '../../../../src/plugins/management/public'; import { PLUGIN, MANAGEMENT_ID } from '../common/constants'; import { init as initUiMetric } from './app/services/track_ui_metric'; import { init as initNotification } from './app/services/notifications'; @@ -22,7 +23,7 @@ export class CrossClusterReplicationPlugin implements Plugin { public setup(coreSetup: CoreSetup, plugins: PluginDependencies) { const { licensing, remoteClusters, usageCollection, management, indexManagement } = plugins; - const esSection = management.sections.getSection('elasticsearch'); + const esSection = management.sections.getSection(ManagementSectionId.Data); const { http, @@ -36,7 +37,7 @@ export class CrossClusterReplicationPlugin implements Plugin { initUiMetric(usageCollection); initNotification(toasts, fatalErrors); - const ccrApp = esSection!.registerApp({ + const ccrApp = esSection.registerApp({ id: MANAGEMENT_ID, title: PLUGIN.TITLE, order: 6, diff --git a/x-pack/plugins/cross_cluster_replication/server/plugin.ts b/x-pack/plugins/cross_cluster_replication/server/plugin.ts index 25c99803480f3..7ef085a21ac1a 100644 --- a/x-pack/plugins/cross_cluster_replication/server/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/server/plugin.ts @@ -15,6 +15,7 @@ import { first } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import { CoreSetup, + ICustomClusterClient, Plugin, Logger, PluginInitializerContext, @@ -36,6 +37,13 @@ interface CrossClusterReplicationContext { client: IScopedClusterClient; } +async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { + const [core] = await getStartServices(); + // Extend the elasticsearchJs client with additional endpoints. + const esClientConfig = { plugins: [elasticsearchJsPlugin] }; + return core.elasticsearch.legacy.createClient('crossClusterReplication', esClientConfig); +} + const ccrDataEnricher = async (indicesList: Index[], callWithRequest: APICaller) => { if (!indicesList?.length) { return indicesList; @@ -69,6 +77,7 @@ export class CrossClusterReplicationServerPlugin implements Plugin; private readonly license: License; private readonly logger: Logger; + private ccrEsClient?: ICustomClusterClient; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); @@ -77,7 +86,7 @@ export class CrossClusterReplicationServerPlugin implements Plugin { + http.registerRouteHandlerContext('crossClusterReplication', async (ctx, request) => { + this.ccrEsClient = this.ccrEsClient ?? (await getCustomEsClient(getStartServices)); return { - client: ccrEsClient.asScoped(request), + client: this.ccrEsClient.asScoped(request), }; }); @@ -135,5 +142,10 @@ export class CrossClusterReplicationServerPlugin implements Plugin testy @@ -748,11 +748,11 @@ exports[`extend index management ilm summary extension should return extension w className="euiDescriptionList__description" > testy diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap index cbc735bb150f5..857a63826505e 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/__snapshots__/policy_table.test.js.snap @@ -94,7 +94,7 @@ exports[`policy table should show empty state when there are not any policies 1` { const [coreStart] = await getStartServices(); const { diff --git a/x-pack/plugins/index_management/common/constants/base_path.ts b/x-pack/plugins/index_management/common/constants/base_path.ts index 3a49e2870b609..c17d96250b1fb 100644 --- a/x-pack/plugins/index_management/common/constants/base_path.ts +++ b/x-pack/plugins/index_management/common/constants/base_path.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const BASE_PATH = '/management/elasticsearch/index_management/'; +export const BASE_PATH = '/management/data/index_management/'; diff --git a/x-pack/plugins/index_management/public/application/services/navigation.ts b/x-pack/plugins/index_management/public/application/services/navigation.ts index e56e8d474d780..8bd9fc2432232 100644 --- a/x-pack/plugins/index_management/public/application/services/navigation.ts +++ b/x-pack/plugins/index_management/public/application/services/navigation.ts @@ -19,8 +19,6 @@ export const getIndexListUri = (filter: any) => { export const getILMPolicyPath = (policyName: string) => { return encodeURI( - `#/management/elasticsearch/index_lifecycle_management/policies/edit/${encodeURIComponent( - policyName - )}` + `#/management/data/index_lifecycle_management/policies/edit/${encodeURIComponent(policyName)}` ); }; diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 78e80687abeb4..5fb8ce7207729 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup } from '../../../../src/core/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { UIM_APP_NAME, PLUGIN } from '../common/constants'; import { httpService } from './application/services/http'; @@ -48,10 +48,10 @@ export class IndexMgmtUIPlugin { notificationService.setup(notifications); this.uiMetricService.setup(usageCollection); - management.sections.getSection('elasticsearch')!.registerApp({ + management.sections.getSection(ManagementSectionId.Data).registerApp({ id: PLUGIN.id, title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }), - order: 2, + order: 0, mount: async params => { const { mountManagementSection } = await import('./application/mount_management_section'); const services = { diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx index 8bcf0e9ed5be5..bafb38459b17b 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/alert_dropdown.tsx @@ -35,7 +35,7 @@ export const MetricsAlertDropdown = () => { icon="tableOfContents" key="manageLink" href={kibana.services?.application?.getUrlForApp( - 'kibana#/management/kibana/triggersActions/alerts' + 'kibana#/management/insightsAndAlerting/triggersActions/alerts' )} > diff --git a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx index d2904206875c7..a3cebcf33f386 100644 --- a/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/components/alerting/inventory/alert_dropdown.tsx @@ -35,7 +35,7 @@ export const InventoryAlertDropdown = () => { icon="tableOfContents" key="manageLink" href={kibana.services?.application?.getUrlForApp( - 'kibana#/management/kibana/triggersActions/alerts' + 'kibana#/management/insightsAndAlerting/triggersActions/alerts' )} > diff --git a/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx b/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx index dd888639b6d07..d808b4f3b64aa 100644 --- a/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx +++ b/x-pack/plugins/infra/public/components/alerting/logs/alert_dropdown.tsx @@ -16,7 +16,7 @@ export const AlertDropdown = () => { const manageAlertsLinkProps = useLinkProps( { app: 'kibana', - hash: 'management/kibana/triggersActions/alerts', + hash: 'management/insightsAndAlerting/triggersActions/alerts', }, { hrefOnly: true, diff --git a/x-pack/plugins/ingest_pipelines/common/constants.ts b/x-pack/plugins/ingest_pipelines/common/constants.ts index edf681c276a84..de291e364e02f 100644 --- a/x-pack/plugins/ingest_pipelines/common/constants.ts +++ b/x-pack/plugins/ingest_pipelines/common/constants.ts @@ -11,7 +11,7 @@ export const PLUGIN_ID = 'ingest_pipelines'; export const PLUGIN_MIN_LICENSE_TYPE = basicLicense; -export const BASE_PATH = '/management/elasticsearch/ingest_pipelines'; +export const BASE_PATH = '/management/ingest/ingest_pipelines'; export const API_BASE_PATH = '/api/ingest_pipelines'; diff --git a/x-pack/plugins/ingest_pipelines/public/plugin.ts b/x-pack/plugins/ingest_pipelines/public/plugin.ts index 0ab46f386e83b..d537f8d68d7e4 100644 --- a/x-pack/plugins/ingest_pipelines/public/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/public/plugin.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin } from 'src/core/public'; +import { ManagementSectionId } from '../../../../src/plugins/management/public'; import { PLUGIN_ID } from '../common/constants'; import { uiMetricService, apiService } from './application/services'; import { Dependencies } from './types'; @@ -20,7 +21,7 @@ export class IngestPipelinesPlugin implements Plugin { uiMetricService.setup(usageCollection); apiService.setup(http, uiMetricService); - management.sections.getSection('elasticsearch')!.registerApp({ + management.sections.getSection(ManagementSectionId.Ingest).registerApp({ id: PLUGIN_ID, order: 1, title: i18n.translate('xpack.ingestPipelines.appTitle', { diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap index 03421e66c77f5..e4411807dfa56 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AddLicense component when license is active should display correct verbiage 1`] = `""`; +exports[`AddLicense component when license is active should display correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; -exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; +exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index 9ac8b14236685..b621e89efbee3 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -958,11 +958,11 @@ exports[`UploadLicense should display a modal when license requires acknowledgem className="euiFlexItem euiFlexItem--flexGrowZero" > { const [core] = await getStartServices(); const initialLicense = await plugins.licensing.license$.pipe(first()).toPromise(); diff --git a/x-pack/plugins/licensing/public/plugin.test.ts b/x-pack/plugins/licensing/public/plugin.test.ts index f68e1dcfaf62b..9f0019680d14b 100644 --- a/x-pack/plugins/licensing/public/plugin.test.ts +++ b/x-pack/plugins/licensing/public/plugin.test.ts @@ -367,7 +367,7 @@ describe('licensing plugin', () => { expect(coreStart.overlays.banners.add).toHaveBeenCalledTimes(1); expect(mountExpiredBannerMock).toHaveBeenCalledWith({ type: 'gold', - uploadUrl: '/app/kibana#/management/elasticsearch/license_management/upload_license', + uploadUrl: '/app/kibana#/management/stack/license_management/upload_license', }); }); }); diff --git a/x-pack/plugins/licensing/public/plugin.ts b/x-pack/plugins/licensing/public/plugin.ts index dab4c4048ce4c..31910d81b3434 100644 --- a/x-pack/plugins/licensing/public/plugin.ts +++ b/x-pack/plugins/licensing/public/plugin.ts @@ -148,7 +148,7 @@ export class LicensingPlugin implements Plugin { private showExpiredBanner(license: ILicense) { const uploadUrl = this.coreStart!.http.basePath.prepend( - '/app/kibana#/management/elasticsearch/license_management/upload_license' + '/app/kibana#/management/stack/license_management/upload_license' ); this.coreStart!.overlays.banners.add( mountExpiredBanner({ diff --git a/x-pack/plugins/lists/common/constants.ts b/x-pack/plugins/lists/common/constants.ts index dbe31fed66413..96d28bf618ce4 100644 --- a/x-pack/plugins/lists/common/constants.ts +++ b/x-pack/plugins/lists/common/constants.ts @@ -5,8 +5,14 @@ */ /** - * Lists routes + * Value list routes */ -export const LIST_URL = `/api/lists`; +export const LIST_URL = '/api/lists'; export const LIST_INDEX = `${LIST_URL}/index`; export const LIST_ITEM_URL = `${LIST_URL}/items`; + +/** + * Exception list routes + */ +export const EXCEPTION_LIST_URL = '/api/exception_lists'; +export const EXCEPTION_LIST_ITEM_URL = '/api/exception_lists/items'; diff --git a/x-pack/plugins/lists/common/schemas/common/schemas.ts b/x-pack/plugins/lists/common/schemas/common/schemas.ts index edc037ed7a0b1..cd69685ffcb1b 100644 --- a/x-pack/plugins/lists/common/schemas/common/schemas.ts +++ b/x-pack/plugins/lists/common/schemas/common/schemas.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { NonEmptyString } from '../types/non_empty_string'; +import { DefaultStringArray, NonEmptyString } from '../types'; export const name = t.string; export type Name = t.TypeOf; @@ -21,8 +21,9 @@ export const descriptionOrUndefined = t.union([description, t.undefined]); export type DescriptionOrUndefined = t.TypeOf; export const list_id = NonEmptyString; +export type ListId = t.TypeOf; export const list_idOrUndefined = t.union([list_id, t.undefined]); -export type List_idOrUndefined = t.TypeOf; +export type ListIdOrUndefined = t.TypeOf; export const item = t.string; export const created_at = t.string; // TODO: Make this into an ISO Date string check @@ -60,3 +61,44 @@ export type MetaOrUndefined = t.TypeOf; export const esDataTypeUnion = t.union([t.type({ ip }), t.type({ keyword })]); export type EsDataTypeUnion = t.TypeOf; + +export const tags = DefaultStringArray; +export type Tags = t.TypeOf; +export const tagsOrUndefined = t.union([tags, t.undefined]); +export type TagsOrUndefined = t.TypeOf; + +export const _tags = DefaultStringArray; +export type _Tags = t.TypeOf; +export const _tagsOrUndefined = t.union([_tags, t.undefined]); +export type _TagsOrUndefined = t.TypeOf; + +// TODO: Change this into a t.keyof enumeration when we know what types of lists we going to have. +export const exceptionListType = t.string; +export const exceptionListTypeOrUndefined = t.union([exceptionListType, t.undefined]); +export type ExceptionListType = t.TypeOf; +export type ExceptionListTypeOrUndefined = t.TypeOf; + +// TODO: Change this into a t.keyof enumeration when we know what types of lists we going to have. +export const exceptionListItemType = t.string; +export type ExceptionListItemType = t.TypeOf; + +export const list_type = t.keyof({ item: null, list: null }); +export type ListType = t.TypeOf; + +// TODO: Investigate what the deep structure of a comment is really going to be and then change this to use that deep structure with a default array +export const comment = DefaultStringArray; +export type Comment = t.TypeOf; +export const commentOrUndefined = t.union([comment, t.undefined]); +export type CommentOrUndefined = t.TypeOf; + +export const item_id = NonEmptyString; +export type ItemId = t.TypeOf; +export const itemIdOrUndefined = t.union([item_id, t.undefined]); +export type ItemIdOrUndefined = t.TypeOf; + +export const per_page = t.number; // TODO: Change this out for PositiveNumber from siem +export const total = t.number; // TODO: Change this out for PositiveNumber from siem +export const page = t.number; // TODO: Change this out for PositiveNumber from siem +export const sort_field = t.string; +export const sort_order = t.keyof({ asc: null, desc: null }); +export const filter = t.string; diff --git a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts b/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts index 8f23f3744e563..d008a82308153 100644 --- a/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_query/update_es_list_schema.ts @@ -16,6 +16,7 @@ import { updated_by, } from '../common/schemas'; +// TODO: Should we use partial here and everywhere these are instead of this OrUndefined? export const updateEsListSchema = t.exact( t.type({ description: descriptionOrUndefined, diff --git a/x-pack/plugins/lists/common/schemas/index.ts b/x-pack/plugins/lists/common/schemas/index.ts index 6a60a6df55691..774378c6318f5 100644 --- a/x-pack/plugins/lists/common/schemas/index.ts +++ b/x-pack/plugins/lists/common/schemas/index.ts @@ -5,7 +5,9 @@ */ export * from './common'; -export * from './request'; -export * from './response'; export * from './elastic_query'; export * from './elastic_response'; +export * from './request'; +export * from './response'; +export * from './saved_objects'; +export * from './types'; diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts new file mode 100644 index 0000000000000..f899fd69110fa --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_item_schema.ts @@ -0,0 +1,65 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + ItemId, + Tags, + _Tags, + _tags, + comment, + description, + exceptionListItemType, + list_id, + meta, + name, + tags, +} from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; +import { DefaultEntryArray, DefaultUuid } from '../types'; +import { EntriesArray } from '../types/entries'; + +export const createExceptionListItemSchema = t.intersection([ + t.exact( + t.type({ + description, + list_id, + name, + type: exceptionListItemType, + }) + ), + t.exact( + t.partial({ + _tags, // defaults to empty array if not set during decode + comment, // defaults to empty array if not set during decode + entries: DefaultEntryArray, // defaults to empty array if not set during decode + item_id: DefaultUuid, // defaults to GUID (uuid v4) if not set during decode + meta, // defaults to undefined if not set during decode + tags, // defaults to empty array if not set during decode + }) + ), +]); + +export type CreateExceptionListItemSchemaPartial = Identity< + t.TypeOf +>; +export type CreateExceptionListItemSchema = RequiredKeepUndefined< + t.TypeOf +>; + +// This type is used after a decode since the arrays turn into defaults of empty arrays +// and if a item_id is not specified it turns into a default GUID +export type CreateExceptionListItemSchemaDecoded = Identity< + Omit & { + _tags: _Tags; + tags: Tags; + item_id: ItemId; + entries: EntriesArray; + } +>; diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts new file mode 100644 index 0000000000000..d38d3cc038525 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.mock.ts @@ -0,0 +1,19 @@ +/* + * 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 { DESCRIPTION, LIST_ID, META, NAME, TYPE } from '../../constants.mock'; + +import { CreateExceptionListSchema } from './create_exception_list_schema'; + +export const getCreateExceptionListSchemaMock = (): CreateExceptionListSchema => ({ + _tags: [], + description: DESCRIPTION, + list_id: LIST_ID, + meta: META, + name: NAME, + tags: [], + type: TYPE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts new file mode 100644 index 0000000000000..f19c50c6b42ae --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.test.ts @@ -0,0 +1,96 @@ +/* + * 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 { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { + CreateExceptionListSchema, + createExceptionListSchema, +} from './create_exception_list_schema'; +import { getCreateExceptionListSchemaMock } from './create_exception_list_schema.mock'; + +describe('create_exception_list_schema', () => { + test('it should validate a typical exception lists request', () => { + const payload = getCreateExceptionListSchemaMock(); + const decoded = createExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for meta', () => { + const payload = getCreateExceptionListSchemaMock(); + delete payload.meta; + const decoded = createExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for tags but return an array', () => { + const inputPayload = getCreateExceptionListSchemaMock(); + const outputPayload = getCreateExceptionListSchemaMock(); + delete inputPayload.tags; + outputPayload.tags = []; + const decoded = createExceptionListSchema.decode(inputPayload); + const checked = exactCheck(inputPayload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(outputPayload); + }); + + test('it should accept an undefined for _tags but return an array', () => { + const inputPayload = getCreateExceptionListSchemaMock(); + const outputPayload = getCreateExceptionListSchemaMock(); + delete inputPayload._tags; + outputPayload._tags = []; + const decoded = createExceptionListSchema.decode(inputPayload); + const checked = exactCheck(inputPayload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(outputPayload); + }); + + test('it should accept an undefined for list_id and auto generate a uuid', () => { + const inputPayload = getCreateExceptionListSchemaMock(); + delete inputPayload.list_id; + const decoded = createExceptionListSchema.decode(inputPayload); + const checked = exactCheck(inputPayload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect((message.schema as CreateExceptionListSchema).list_id).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i + ); + }); + + test('it should accept an undefined for list_id and generate a correct body not counting the uuid', () => { + const inputPayload = getCreateExceptionListSchemaMock(); + delete inputPayload.list_id; + const decoded = createExceptionListSchema.decode(inputPayload); + const checked = exactCheck(inputPayload, decoded); + const message = pipe(checked, foldLeftRight); + delete (message.schema as CreateExceptionListSchema).list_id; + expect(message.schema).toEqual(inputPayload); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: CreateExceptionListSchema & { + extraKey?: string; + } = getCreateExceptionListSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = createExceptionListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts new file mode 100644 index 0000000000000..5ba3bf4e8f43b --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_exception_list_schema.ts @@ -0,0 +1,55 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + ListId, + Tags, + _Tags, + _tags, + description, + exceptionListType, + meta, + name, + tags, +} from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; +import { DefaultUuid } from '../types/default_uuid'; + +export const createExceptionListSchema = t.intersection([ + t.exact( + t.type({ + description, + name, + type: exceptionListType, + }) + ), + t.exact( + t.partial({ + _tags, // defaults to empty array if not set during decode + list_id: DefaultUuid, // defaults to a GUID (UUID v4) string if not set during decode + meta, // defaults to undefined if not set during decode + tags, // defaults to empty array if not set during decode + }) + ), +]); + +export type CreateExceptionListSchemaPartial = Identity>; +export type CreateExceptionListSchema = RequiredKeepUndefined< + t.TypeOf +>; + +// This type is used after a decode since the arrays turn into defaults of empty arrays. +export type CreateExceptionListSchemaDecoded = Identity< + CreateExceptionListSchema & { + _tags: _Tags; + tags: Tags; + list_id: ListId; + } +>; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts index 6cba81e47fbcc..6d16f2074864c 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { idOrUndefined, list_id, metaOrUndefined, value } from '../common/schemas'; +import { id, list_id, meta, value } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const createListItemSchema = t.intersection([ @@ -18,7 +18,7 @@ export const createListItemSchema = t.intersection([ value, }) ), - t.exact(t.partial({ id: idOrUndefined, meta: metaOrUndefined })), + t.exact(t.partial({ id, meta })), ]); export type CreateListItemSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts index 7a6e2a707873c..68df80b2a42dd 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts @@ -6,7 +6,7 @@ import * as t from 'io-ts'; -import { description, idOrUndefined, metaOrUndefined, name, type } from '../common/schemas'; +import { description, id, meta, name, type } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const createListSchema = t.intersection([ @@ -17,7 +17,7 @@ export const createListSchema = t.intersection([ type, }) ), - t.exact(t.partial({ id: idOrUndefined, meta: metaOrUndefined })), + t.exact(t.partial({ id, meta })), ]); export type CreateListSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts new file mode 100644 index 0000000000000..607e05ef8286f --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_item_schema.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { id, item_id } from '../common/schemas'; + +export const deleteExceptionListItemSchema = t.exact( + t.partial({ + id, + item_id, + }) +); + +export type DeleteExceptionListItemSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts new file mode 100644 index 0000000000000..7a6086514f943 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/delete_exception_list_schema.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { id, list_id } from '../common/schemas'; + +export const deleteExceptionListSchema = t.exact( + t.partial({ + id, + list_id, + }) +); + +export type DeleteExceptionListSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts index 96f054b304962..91887395e747d 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { idOrUndefined, list_idOrUndefined, valueOrUndefined } from '../common/schemas'; +import { id, list_id, valueOrUndefined } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const deleteListItemSchema = t.intersection([ @@ -17,7 +17,7 @@ export const deleteListItemSchema = t.intersection([ value: valueOrUndefined, }) ), - t.exact(t.partial({ id: idOrUndefined, list_id: list_idOrUndefined })), + t.exact(t.partial({ id, list_id })), ]); export type DeleteListItemSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts new file mode 100644 index 0000000000000..3fc51dd20b0b3 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_item_schema.ts @@ -0,0 +1,35 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { filter, list_id, page, per_page, sort_field, sort_order } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; + +export const findExceptionListItemSchema = t.intersection([ + t.exact( + t.type({ + list_id, + }) + ), + t.exact( + t.partial({ + filter, // defaults to undefined if not set during decode + page, // defaults to undefined if not set during decode + per_page, // defaults to undefined if not set during decode + sort_field, // defaults to undefined if not set during decode + sort_order, // defaults to undefined if not set during decode + }) + ), +]); + +export type FindExceptionListItemSchemaPartial = t.TypeOf; + +export type FindExceptionListItemSchema = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts new file mode 100644 index 0000000000000..f795be9493fbf --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/find_exception_list_schema.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { filter, page, per_page, sort_field, sort_order } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; + +export const findExceptionListSchema = t.exact( + t.partial({ + filter, // defaults to undefined if not set during decode + page, // defaults to undefined if not set during decode + per_page, // defaults to undefined if not set during decode + sort_field, // defaults to undefined if not set during decode + sort_order, // defaults to undefined if not set during decode + }) +); + +export type FindExceptionListSchemaPartial = t.TypeOf; + +export type FindExceptionListSchema = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts index c1745dda7afab..73d9a53a41e4f 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts @@ -8,12 +8,10 @@ import * as t from 'io-ts'; -import { list_idOrUndefined, typeOrUndefined } from '../common/schemas'; +import { list_id, type } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; -export const importListItemQuerySchema = t.exact( - t.partial({ list_id: list_idOrUndefined, type: typeOrUndefined }) -); +export const importListItemQuerySchema = t.exact(t.partial({ list_id, type })); export type ImportListItemQuerySchemaPartial = Identity>; export type ImportListItemQuerySchema = RequiredKeepUndefined< diff --git a/x-pack/plugins/lists/common/schemas/request/index.ts b/x-pack/plugins/lists/common/schemas/request/index.ts index d332ab1eb1bab..0dbd9297b773e 100644 --- a/x-pack/plugins/lists/common/schemas/request/index.ts +++ b/x-pack/plugins/lists/common/schemas/request/index.ts @@ -4,16 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './create_exception_list_item_schema'; +export * from './create_exception_list_schema'; export * from './create_list_item_schema'; export * from './create_list_schema'; +export * from './delete_exception_list_item_schema'; +export * from './delete_exception_list_schema'; export * from './delete_list_item_schema'; export * from './delete_list_schema'; export * from './export_list_item_query_schema'; +export * from './find_exception_list_item_schema'; +export * from './find_exception_list_schema'; export * from './import_list_item_schema'; export * from './patch_list_item_schema'; export * from './patch_list_schema'; +export * from './read_exception_list_item_schema'; +export * from './read_exception_list_schema'; export * from './read_list_item_schema'; export * from './read_list_schema'; +export * from './update_exception_list_item_schema'; +export * from './update_exception_list_schema'; export * from './import_list_item_query_schema'; export * from './update_list_schema'; +export * from './update_exception_list_schema'; export * from './update_list_item_schema'; diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts index 536931f715f3f..2016069f32fb3 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { id, metaOrUndefined, valueOrUndefined } from '../common/schemas'; +import { id, meta, value } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const patchListItemSchema = t.intersection([ @@ -17,7 +17,7 @@ export const patchListItemSchema = t.intersection([ id, }) ), - t.exact(t.partial({ meta: metaOrUndefined, value: valueOrUndefined })), + t.exact(t.partial({ meta, value })), ]); export type PatchListItemSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts index 59d1a66a581a0..653a42dc91653 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { descriptionOrUndefined, id, metaOrUndefined, nameOrUndefined } from '../common/schemas'; +import { description, id, meta, name } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const patchListSchema = t.intersection([ @@ -17,9 +17,7 @@ export const patchListSchema = t.intersection([ id, }) ), - t.exact( - t.partial({ description: descriptionOrUndefined, meta: metaOrUndefined, name: nameOrUndefined }) - ), + t.exact(t.partial({ description, meta, name })), ]); export type PatchListSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts new file mode 100644 index 0000000000000..095fcd2f63b48 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/read_exception_list_item_schema.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { id, item_id } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; + +export const readExceptionListItemSchema = t.partial({ + id, + item_id, +}); + +export type ReadExceptionListItemSchemaPartial = t.TypeOf; +export type ReadExceptionListItemSchema = RequiredKeepUndefined; diff --git a/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts new file mode 100644 index 0000000000000..5593e640f71ac --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/read_exception_list_schema.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { id, list_id } from '../common/schemas'; +import { RequiredKeepUndefined } from '../../types'; + +export const readExceptionListSchema = t.partial({ + id, + list_id, +}); + +export type ReadExceptionListSchemaPartial = t.TypeOf; +export type ReadExceptionListSchema = RequiredKeepUndefined; diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts index b69523b664fd7..394c1f1e2289a 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts @@ -8,12 +8,10 @@ import * as t from 'io-ts'; -import { idOrUndefined, list_idOrUndefined, valueOrUndefined } from '../common/schemas'; +import { id, list_id, value } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; -export const readListItemSchema = t.exact( - t.partial({ id: idOrUndefined, list_id: list_idOrUndefined, value: valueOrUndefined }) -); +export const readListItemSchema = t.exact(t.partial({ id, list_id, value })); export type ReadListItemSchemaPartial = Identity>; export type ReadListItemSchema = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts new file mode 100644 index 0000000000000..162406a6d6589 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_item_schema.ts @@ -0,0 +1,63 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + Tags, + _Tags, + _tags, + comment, + description, + exceptionListItemType, + id, + meta, + name, + tags, +} from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; +import { DefaultEntryArray } from '../types'; +import { EntriesArray } from '../types/entries'; + +export const updateExceptionListItemSchema = t.intersection([ + t.exact( + t.type({ + description, + name, + type: exceptionListItemType, + }) + ), + t.exact( + t.partial({ + _tags, // defaults to empty array if not set during decode + comment, // defaults to empty array if not set during decode + entries: DefaultEntryArray, // defaults to empty array if not set during decode + id, // defaults to undefined if not set during decode + item_id: t.union([t.string, t.undefined]), + meta, // defaults to undefined if not set during decode + tags, // defaults to empty array if not set during decode + }) + ), +]); + +export type UpdateExceptionListItemSchemaPartial = Identity< + t.TypeOf +>; +export type UpdateExceptionListItemSchema = RequiredKeepUndefined< + t.TypeOf +>; + +// This type is used after a decode since the arrays turn into defaults of empty arrays +// and if a item_id is not specified it turns into a default GUID +export type UpdateExceptionListItemSchemaDecoded = Identity< + Omit & { + _tags: _Tags; + tags: Tags; + entries: EntriesArray; + } +>; diff --git a/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts new file mode 100644 index 0000000000000..e8a0dcd4994a2 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/update_exception_list_schema.ts @@ -0,0 +1,53 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + Tags, + _Tags, + _tags, + description, + exceptionListType, + meta, + name, + tags, +} from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; + +export const updateExceptionListSchema = t.intersection([ + t.exact( + t.type({ + description, + name, + type: exceptionListType, + }) + ), + t.exact( + t.partial({ + _tags, // defaults to empty array if not set during decode + id: t.union([t.string, t.undefined]), // defaults to undefined if not set during decode + list_id: t.union([t.string, t.undefined]), // defaults to undefined if not set during decode + meta, // defaults to undefined if not set during decode + tags, // defaults to empty array if not set during decode + }) + ), +]); + +export type UpdateExceptionListSchemaPartial = Identity>; +export type UpdateExceptionListSchema = RequiredKeepUndefined< + t.TypeOf +>; + +// This type is used after a decode since the arrays turn into defaults of empty arrays. +export type UpdateExceptionListSchemaDecoded = Identity< + Omit & { + _tags: _Tags; + tags: Tags; + } +>; diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts index 23701ff753bc0..3a42cf28665f5 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { id, metaOrUndefined, value } from '../common/schemas'; +import { id, meta, value } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const updateListItemSchema = t.intersection([ @@ -18,7 +18,11 @@ export const updateListItemSchema = t.intersection([ value, }) ), - t.exact(t.partial({ meta: metaOrUndefined })), + t.exact( + t.partial({ + meta, // defaults to undefined if not set during decode + }) + ), ]); export type UpdateListItemSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts index 8223a6a34b771..4c5c8c429a14c 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; -import { description, id, metaOrUndefined, name } from '../common/schemas'; +import { description, id, meta, name } from '../common/schemas'; import { Identity, RequiredKeepUndefined } from '../../types'; export const updateListSchema = t.intersection([ @@ -19,7 +19,11 @@ export const updateListSchema = t.intersection([ name, }) ), - t.exact(t.partial({ meta: metaOrUndefined })), + t.exact( + t.partial({ + meta, // defaults to undefined if not set during decode + }) + ), ]); export type UpdateListSchemaPartial = Identity>; diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts new file mode 100644 index 0000000000000..15e1c92c06d13 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_item_schema.ts @@ -0,0 +1,52 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + _tags, + commentOrUndefined, + created_at, + created_by, + description, + exceptionListItemType, + id, + item_id, + list_id, + metaOrUndefined, + name, + tags, + tie_breaker_id, + updated_at, + updated_by, +} from '../common/schemas'; +import { entriesArray } from '../types'; + +// TODO: Should we use a partial here to reflect that this can JSON serialize meta, comment as non existent? +export const exceptionListItemSchema = t.exact( + t.type({ + _tags, + comment: commentOrUndefined, + created_at, + created_by, + description, + entries: entriesArray, + id, + item_id, + list_id, + meta: metaOrUndefined, + name, + tags, + tie_breaker_id, + type: exceptionListItemType, + updated_at, + updated_by, + }) +); + +export type ExceptionListItemSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts new file mode 100644 index 0000000000000..1940d94597dec --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.ts @@ -0,0 +1,46 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { + _tags, + created_at, + created_by, + description, + exceptionListType, + id, + list_id, + metaOrUndefined, + name, + tags, + tie_breaker_id, + updated_at, + updated_by, +} from '../common/schemas'; + +// TODO: Should we use a partial here to reflect that this can JSON serialize meta as non existent? +export const exceptionListSchema = t.exact( + t.type({ + _tags, + created_at, + created_by, + description, + id, + list_id, + meta: metaOrUndefined, + name, + tags, + tie_breaker_id, + type: exceptionListType, + updated_at, + updated_by, + }) +); + +export type ExceptionListSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.ts new file mode 100644 index 0000000000000..a58bf433017e6 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { page, per_page, total } from '../common/schemas'; + +import { exceptionListItemSchema } from './exception_list_item_schema'; + +export const foundExceptionListItemSchema = t.exact( + t.type({ + data: t.array(exceptionListItemSchema), + page, + per_page, + total, + }) +); + +export type FoundExceptionListItemSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.ts b/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.ts new file mode 100644 index 0000000000000..a2ea09a3263ae --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/found_exception_list_schema.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { page, per_page, total } from '../common/schemas'; + +import { exceptionListSchema } from './exception_list_schema'; + +export const foundExceptionListSchema = t.exact( + t.type({ + data: t.array(exceptionListSchema), + page, + per_page, + total, + }) +); + +export type FoundExceptionListSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/response/index.ts b/x-pack/plugins/lists/common/schemas/response/index.ts index 3f11adf58d8d4..213685d1183bd 100644 --- a/x-pack/plugins/lists/common/schemas/response/index.ts +++ b/x-pack/plugins/lists/common/schemas/response/index.ts @@ -8,3 +8,7 @@ export * from './list_item_schema'; export * from './list_schema'; export * from './acknowledge_schema'; export * from './list_item_index_exist_schema'; +export * from './exception_list_schema'; +export * from './found_exception_list_item_schema'; +export * from './found_exception_list_schema'; +export * from './exception_list_item_schema'; diff --git a/x-pack/plugins/lists/common/schemas/saved_objects/exceptions_list_so_schema.ts b/x-pack/plugins/lists/common/schemas/saved_objects/exceptions_list_so_schema.ts new file mode 100644 index 0000000000000..a08bab65c881c --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/saved_objects/exceptions_list_so_schema.ts @@ -0,0 +1,50 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/camelcase */ + +import * as t from 'io-ts'; + +import { entriesArrayOrUndefined } from '../types'; +import { + _tags, + commentOrUndefined, + created_at, + created_by, + description, + exceptionListItemType, + exceptionListType, + itemIdOrUndefined, + list_id, + list_type, + metaOrUndefined, + name, + tags, + tie_breaker_id, + updated_by, +} from '../common/schemas'; + +export const exceptionListSoSchema = t.exact( + t.type({ + _tags, + comment: commentOrUndefined, + created_at, + created_by, + description, + entries: entriesArrayOrUndefined, + item_id: itemIdOrUndefined, + list_id, + list_type, + meta: metaOrUndefined, + name, + tags, + tie_breaker_id, + type: t.union([exceptionListType, exceptionListItemType]), + updated_by, + }) +); + +export type ExceptionListSoSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/saved_objects/index.ts b/x-pack/plugins/lists/common/schemas/saved_objects/index.ts new file mode 100644 index 0000000000000..8d42be2d145ca --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/saved_objects/index.ts @@ -0,0 +1,6 @@ +/* + * 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. + */ +export * from './exceptions_list_so_schema'; diff --git a/x-pack/plugins/lists/common/schemas/types/default_entries_array.ts b/x-pack/plugins/lists/common/schemas/types/default_entries_array.ts new file mode 100644 index 0000000000000..43698665bb371 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/default_entries_array.ts @@ -0,0 +1,28 @@ +/* + * 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 * as t from 'io-ts'; +import { Either } from 'fp-ts/lib/Either'; + +import { EntriesArray, entries } from './entries'; + +export type DefaultEntriesArrayC = t.Type; + +/** + * Types the DefaultEntriesArray as: + * - If null or undefined, then a default array of type entry will be set + */ +export const DefaultEntryArray: DefaultEntriesArrayC = new t.Type< + EntriesArray, + EntriesArray, + unknown +>( + 'DefaultEntryArray', + t.array(entries).is, + (input): Either => + input == null ? t.success([]) : t.array(entries).decode(input), + t.identity +); diff --git a/x-pack/plugins/lists/common/schemas/types/default_string_array.ts b/x-pack/plugins/lists/common/schemas/types/default_string_array.ts new file mode 100644 index 0000000000000..c6ebffa379903 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/default_string_array.ts @@ -0,0 +1,22 @@ +/* + * 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 * as t from 'io-ts'; +import { Either } from 'fp-ts/lib/Either'; + +export type DefaultStringArrayC = t.Type; + +/** + * Types the DefaultStringArray as: + * - If null or undefined, then a default array will be set + */ +export const DefaultStringArray: DefaultStringArrayC = new t.Type( + 'DefaultArray', + t.array(t.string).is, + (input): Either => + input == null ? t.success([]) : t.array(t.string).decode(input), + t.identity +); diff --git a/x-pack/plugins/lists/common/schemas/types/default_uuid.ts b/x-pack/plugins/lists/common/schemas/types/default_uuid.ts new file mode 100644 index 0000000000000..4fb4133d7353f --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/default_uuid.ts @@ -0,0 +1,26 @@ +/* + * 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 * as t from 'io-ts'; +import { Either } from 'fp-ts/lib/Either'; +import uuid from 'uuid'; + +import { NonEmptyString } from './non_empty_string'; + +export type DefaultUuidC = t.Type; + +/** + * Types the DefaultUuid as: + * - If null or undefined, then a default string uuid.v4() will be + * created otherwise it will be checked just against an empty string + */ +export const DefaultUuid: DefaultUuidC = new t.Type( + 'DefaultUuid', + t.string.is, + (input): Either => + input == null ? t.success(uuid.v4()) : NonEmptyString.decode(input), + t.identity +); diff --git a/x-pack/plugins/lists/common/schemas/types/entries.ts b/x-pack/plugins/lists/common/schemas/types/entries.ts new file mode 100644 index 0000000000000..750e04e2f39e5 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/entries.ts @@ -0,0 +1,21 @@ +/* + * 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 * as t from 'io-ts'; + +export const entries = t.exact( + t.type({ + field: t.string, + match: t.union([t.string, t.undefined]), + match_any: t.union([t.array(t.string), t.undefined]), + operator: t.string, // TODO: Use a key of with all possible values + }) +); + +export const entriesArray = t.array(entries); +export type EntriesArray = t.TypeOf; +export type Entries = t.TypeOf; +export const entriesArrayOrUndefined = t.union([entriesArray, t.undefined]); +export type EntriesArrayOrUndefined = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/types/index.ts b/x-pack/plugins/lists/common/schemas/types/index.ts new file mode 100644 index 0000000000000..674d7c40c2970 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/types/index.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ +export * from './default_entries_array'; +export * from './default_string_array'; +export * from './default_uuid'; +export * from './entries'; +export * from './non_empty_string'; diff --git a/x-pack/plugins/lists/common/siem_common_deps.ts b/x-pack/plugins/lists/common/siem_common_deps.ts index c5242843dfbdd..eb4b393a36da3 100644 --- a/x-pack/plugins/lists/common/siem_common_deps.ts +++ b/x-pack/plugins/lists/common/siem_common_deps.ts @@ -4,7 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { getPaths, foldLeftRight } from '../../siem/server/utils/build_validation/__mocks__/utils'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { exactCheck } from '../../siem/server/utils/build_validation/exact_check'; +export { exactCheck } from '../../siem/common/exact_check'; +export { getPaths, foldLeftRight } from '../../siem/common/test_utils'; diff --git a/x-pack/plugins/lists/server/plugin.ts b/x-pack/plugins/lists/server/plugin.ts index 5facf981c098e..ed515757875be 100644 --- a/x-pack/plugins/lists/server/plugin.ts +++ b/x-pack/plugins/lists/server/plugin.ts @@ -13,7 +13,7 @@ import { SpacesServiceSetup } from '../../spaces/server'; import { ConfigType } from './config'; import { initRoutes } from './routes/init_routes'; -import { ListClient } from './services/lists/client'; +import { ListClient } from './services/lists/list_client'; import { ContextProvider, ContextProviderReturn, @@ -24,6 +24,8 @@ import { import { createConfig$ } from './create_config'; import { getSpaceId } from './get_space_id'; import { getUser } from './get_user'; +import { initSavedObjects } from './saved_objects'; +import { ExceptionListClient } from './services/exception_lists/exception_list_client'; export class ListPlugin implements Plugin, ListsPluginStart, PluginsSetup> { @@ -48,14 +50,22 @@ export class ListPlugin this.config = config; this.security = plugins.security; + initSavedObjects(core.savedObjects); + core.http.registerRouteHandlerContext('lists', this.createRouteHandlerContext()); const router = core.http.createRouter(); initRoutes(router); return { - getListClient: (apiCaller, spaceId, user): ListClient => { + getExceptionListClient: (savedObjectsClient, user): ExceptionListClient => { + return new ExceptionListClient({ + savedObjectsClient, + user, + }); + }, + getListClient: (callCluster, spaceId, user): ListClient => { return new ListClient({ - callCluster: apiCaller, + callCluster, config, spaceId, user, @@ -77,8 +87,9 @@ export class ListPlugin const { spaces, config, security } = this; const { core: { + savedObjects: { client: savedObjectsClient }, elasticsearch: { - dataClient: { callAsCurrentUser }, + dataClient: { callAsCurrentUser: callCluster }, }, }, } = context; @@ -88,9 +99,14 @@ export class ListPlugin const spaceId = getSpaceId({ request, spaces }); const user = getUser({ request, security }); return { + getExceptionListClient: (): ExceptionListClient => + new ExceptionListClient({ + savedObjectsClient, + user, + }), getListClient: (): ListClient => new ListClient({ - callCluster: callAsCurrentUser, + callCluster, config, spaceId, user, diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts new file mode 100644 index 0000000000000..ddcae137a961a --- /dev/null +++ b/x-pack/plugins/lists/server/routes/create_exception_list_item_route.ts @@ -0,0 +1,109 @@ +/* + * 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 { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { + CreateExceptionListItemSchemaDecoded, + createExceptionListItemSchema, + exceptionListItemSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from './utils/get_exception_list_client'; + +export const createExceptionListItemRoute = (router: IRouter): void => { + router.post( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_ITEM_URL, + validate: { + body: buildRouteValidation< + typeof createExceptionListItemSchema, + CreateExceptionListItemSchemaDecoded + >(createExceptionListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { + name, + _tags, + tags, + meta, + comment, + description, + entries, + item_id: itemId, + list_id: listId, + type, + } = request.body; + const exceptionLists = getExceptionListClient(context); + const exceptionList = await exceptionLists.getExceptionList({ + id: undefined, + listId, + // TODO: Expose the name space type + namespaceType: 'single', + }); + if (exceptionList == null) { + return siemResponse.error({ + body: `list id: "${listId}" does not exist`, + statusCode: 404, + }); + } else { + const exceptionListItem = await exceptionLists.getExceptionListItem({ + id: undefined, + itemId, + // TODO: Expose the name space type + namespaceType: 'single', + }); + if (exceptionListItem != null) { + return siemResponse.error({ + body: `exception list item id: "${itemId}" already exists`, + statusCode: 409, + }); + } else { + const createdList = await exceptionLists.createExceptionListItem({ + _tags, + comment, + description, + entries, + itemId, + listId, + meta, + name, + // TODO: Expose the name space type + namespaceType: 'single', + tags, + type, + }); + const [validated, errors] = validate(createdList, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/create_exception_list_route.ts b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts new file mode 100644 index 0000000000000..c8a1b080c16f6 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/create_exception_list_route.ts @@ -0,0 +1,82 @@ +/* + * 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 { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { + CreateExceptionListSchemaDecoded, + createExceptionListSchema, + exceptionListSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from './utils/get_exception_list_client'; + +export const createExceptionListRoute = (router: IRouter): void => { + router.post( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_URL, + validate: { + body: buildRouteValidation< + typeof createExceptionListSchema, + CreateExceptionListSchemaDecoded + >(createExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { name, _tags, tags, meta, description, list_id: listId, type } = request.body; + const exceptionLists = getExceptionListClient(context); + const exceptionList = await exceptionLists.getExceptionList({ + id: undefined, + listId, + // TODO: Expose the name space type + namespaceType: 'single', + }); + if (exceptionList != null) { + return siemResponse.error({ + body: `exception list id: "${listId}" already exists`, + statusCode: 409, + }); + } else { + const createdList = await exceptionLists.createExceptionList({ + _tags, + description, + listId, + meta, + name, + // TODO: Expose the name space type + namespaceType: 'single', + tags, + type, + }); + const [validated, errors] = validate(createdList, exceptionListSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.ts new file mode 100644 index 0000000000000..e10ffab5359b0 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_item_route.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 { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { deleteExceptionListItemSchema, exceptionListItemSchema } from '../../common/schemas'; + +import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils'; + +export const deleteExceptionListItemRoute = (router: IRouter): void => { + router.delete( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_ITEM_URL, + validate: { + query: buildRouteValidation(deleteExceptionListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const exceptionLists = getExceptionListClient(context); + const { item_id: itemId, id } = request.query; + if (itemId == null && id == null) { + return siemResponse.error({ + body: 'Either "item_id" or "id" needs to be defined in the request', + statusCode: 400, + }); + } else { + const deleted = await exceptionLists.deleteExceptionListItem({ + id, + itemId, + namespaceType: 'single', // TODO: Bubble this up + }); + if (deleted == null) { + return siemResponse.error({ + body: getErrorMessageExceptionListItem({ id, itemId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(deleted, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts new file mode 100644 index 0000000000000..ef30ab6ab64c5 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/delete_exception_list_route.ts @@ -0,0 +1,71 @@ +/* + * 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 { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { deleteExceptionListSchema, exceptionListSchema } from '../../common/schemas'; + +import { getErrorMessageExceptionList, getExceptionListClient } from './utils'; + +export const deleteExceptionListRoute = (router: IRouter): void => { + router.delete( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_URL, + validate: { + query: buildRouteValidation(deleteExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const exceptionLists = getExceptionListClient(context); + const { list_id: listId, id } = request.query; + if (listId == null && id == null) { + return siemResponse.error({ + body: 'Either "list_id" or "id" needs to be defined in the request', + statusCode: 400, + }); + } else { + // TODO: At the moment this will delete the list but we need to delete all the list items before deleting the list + const deleted = await exceptionLists.deleteExceptionList({ + id, + listId, + namespaceType: 'single', + }); + if (deleted == null) { + return siemResponse.error({ + body: getErrorMessageExceptionList({ id, listId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(deleted, exceptionListSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts index 51b4eb9f02cc2..82dfe8a4f29d0 100644 --- a/x-pack/plugins/lists/server/routes/delete_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/delete_list_item_route.ts @@ -73,7 +73,7 @@ export const deleteListItemRoute = (router: IRouter): void => { } } else { return siemResponse.error({ - body: `Either "list_id" or "id" needs to be defined in the request`, + body: 'Either "list_id" or "id" needs to be defined in the request', statusCode: 400, }); } diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts new file mode 100644 index 0000000000000..3b5503ffb9833 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/find_exception_list_item_route.ts @@ -0,0 +1,73 @@ +/* + * 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 { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { findExceptionListItemSchema, foundExceptionListItemSchema } from '../../common/schemas'; + +import { getExceptionListClient } from './utils'; + +export const findExceptionListItemRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists'], + }, + path: `${EXCEPTION_LIST_ITEM_URL}/_find`, + validate: { + query: buildRouteValidation(findExceptionListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const exceptionLists = getExceptionListClient(context); + const { + filter, + list_id: listId, + page, + per_page: perPage, + sort_field: sortField, + sort_order: sortOrder, + } = request.query; + const exceptionListItems = await exceptionLists.findExceptionListItem({ + filter, + listId, + namespaceType: 'single', // TODO: Bubble this up + page, + perPage, + sortField, + sortOrder, + }); + if (exceptionListItems == null) { + return siemResponse.error({ + body: `list id: "${listId}" does not exist`, + statusCode: 404, + }); + } + const [validated, errors] = validate(exceptionListItems, foundExceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/find_exception_list_route.ts b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts new file mode 100644 index 0000000000000..41c0c0760e03b --- /dev/null +++ b/x-pack/plugins/lists/server/routes/find_exception_list_route.ts @@ -0,0 +1,65 @@ +/* + * 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 { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { findExceptionListSchema, foundExceptionListSchema } from '../../common/schemas'; + +import { getExceptionListClient } from './utils'; + +export const findExceptionListRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists'], + }, + path: `${EXCEPTION_LIST_URL}/_find`, + validate: { + query: buildRouteValidation(findExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const exceptionLists = getExceptionListClient(context); + const { + filter, + page, + per_page: perPage, + sort_field: sortField, + sort_order: sortOrder, + } = request.query; + const exceptionListItems = await exceptionLists.findExceptionList({ + filter, + namespaceType: 'single', // TODO: Bubble this up + page, + perPage, + sortField, + sortOrder, + }); + const [validated, errors] = validate(exceptionListItems, foundExceptionListSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/index.ts b/x-pack/plugins/lists/server/routes/index.ts index 4951cddc56939..97f497bca7183 100644 --- a/x-pack/plugins/lists/server/routes/index.ts +++ b/x-pack/plugins/lists/server/routes/index.ts @@ -4,18 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './create_exception_list_item_route'; +export * from './create_exception_list_route'; export * from './create_list_index_route'; export * from './create_list_item_route'; export * from './create_list_route'; +export * from './delete_exception_list_route'; +export * from './delete_exception_list_item_route'; export * from './delete_list_index_route'; export * from './delete_list_item_route'; export * from './delete_list_route'; export * from './export_list_item_route'; +export * from './find_exception_list_item_route'; +export * from './find_exception_list_route'; export * from './import_list_item_route'; export * from './init_routes'; export * from './patch_list_item_route'; export * from './patch_list_route'; +export * from './read_exception_list_item_route'; +export * from './read_exception_list_route'; export * from './read_list_index_route'; export * from './read_list_item_route'; export * from './read_list_route'; +export * from './update_exception_list_item_route'; +export * from './update_exception_list_route'; +export * from './update_list_item_route'; +export * from './update_list_route'; export * from './utils'; diff --git a/x-pack/plugins/lists/server/routes/init_routes.ts b/x-pack/plugins/lists/server/routes/init_routes.ts index 924dd086ee708..16f96d99505d8 100644 --- a/x-pack/plugins/lists/server/routes/init_routes.ts +++ b/x-pack/plugins/lists/server/routes/init_routes.ts @@ -6,23 +6,32 @@ import { IRouter } from 'kibana/server'; -import { updateListRoute } from './update_list_route'; -import { updateListItemRoute } from './update_list_item_route'; - import { + createExceptionListItemRoute, + createExceptionListRoute, createListIndexRoute, createListItemRoute, createListRoute, + deleteExceptionListItemRoute, + deleteExceptionListRoute, deleteListIndexRoute, deleteListItemRoute, deleteListRoute, exportListItemRoute, + findExceptionListItemRoute, + findExceptionListRoute, importListItemRoute, patchListItemRoute, patchListRoute, + readExceptionListItemRoute, + readExceptionListRoute, readListIndexRoute, readListItemRoute, readListRoute, + updateExceptionListItemRoute, + updateExceptionListRoute, + updateListItemRoute, + updateListRoute, } from '.'; export const initRoutes = (router: IRouter): void => { @@ -33,7 +42,7 @@ export const initRoutes = (router: IRouter): void => { deleteListRoute(router); patchListRoute(router); - // lists items + // list items createListItemRoute(router); readListItemRoute(router); updateListItemRoute(router); @@ -46,4 +55,18 @@ export const initRoutes = (router: IRouter): void => { createListIndexRoute(router); readListIndexRoute(router); deleteListIndexRoute(router); + + // exception lists + createExceptionListRoute(router); + readExceptionListRoute(router); + updateExceptionListRoute(router); + deleteExceptionListRoute(router); + findExceptionListRoute(router); + + // exception list items + createExceptionListItemRoute(router); + readExceptionListItemRoute(router); + updateExceptionListItemRoute(router); + deleteExceptionListItemRoute(router); + findExceptionListItemRoute(router); }; diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts new file mode 100644 index 0000000000000..77d37373549c7 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/read_exception_list_item_route.ts @@ -0,0 +1,68 @@ +/* + * 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 { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { exceptionListItemSchema, readExceptionListItemSchema } from '../../common/schemas'; + +import { getErrorMessageExceptionListItem, getExceptionListClient } from './utils'; + +export const readExceptionListItemRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_ITEM_URL, + validate: { + query: buildRouteValidation(readExceptionListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { id, item_id: itemId } = request.query; + const exceptionLists = getExceptionListClient(context); + if (id != null || itemId != null) { + const exceptionListItem = await exceptionLists.getExceptionListItem({ + id, + itemId, + // TODO: Bubble this up + namespaceType: 'single', + }); + if (exceptionListItem == null) { + return siemResponse.error({ + body: getErrorMessageExceptionListItem({ id, itemId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(exceptionListItem, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } else { + return siemResponse.error({ body: 'id or item_id required', statusCode: 400 }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/read_exception_list_route.ts b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts new file mode 100644 index 0000000000000..1668124acdfce --- /dev/null +++ b/x-pack/plugins/lists/server/routes/read_exception_list_route.ts @@ -0,0 +1,68 @@ +/* + * 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 { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { exceptionListSchema, readExceptionListSchema } from '../../common/schemas'; + +import { getErrorMessageExceptionList, getExceptionListClient } from './utils'; + +export const readExceptionListRoute = (router: IRouter): void => { + router.get( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_URL, + validate: { + query: buildRouteValidation(readExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { id, list_id: listId } = request.query; + const exceptionLists = getExceptionListClient(context); + if (id != null || listId != null) { + const exceptionList = await exceptionLists.getExceptionList({ + id, + listId, + // TODO: Bubble this up + namespaceType: 'single', + }); + if (exceptionList == null) { + return siemResponse.error({ + body: getErrorMessageExceptionList({ id, listId }), + statusCode: 404, + }); + } else { + const [validated, errors] = validate(exceptionList, exceptionListSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } else { + return siemResponse.error({ body: 'id or list_id required', statusCode: 400 }); + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/read_list_item_route.ts b/x-pack/plugins/lists/server/routes/read_list_item_route.ts index 0a60cba786f04..10c7f781f554c 100644 --- a/x-pack/plugins/lists/server/routes/read_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/read_list_item_route.ts @@ -77,7 +77,7 @@ export const readListItemRoute = (router: IRouter): void => { } } else { return siemResponse.error({ - body: `Either "list_id" or "id" needs to be defined in the request`, + body: 'Either "list_id" or "id" needs to be defined in the request', statusCode: 400, }); } diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts new file mode 100644 index 0000000000000..478225ee35eb8 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/update_exception_list_item_route.ts @@ -0,0 +1,89 @@ +/* + * 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 { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_ITEM_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { + UpdateExceptionListItemSchemaDecoded, + exceptionListItemSchema, + updateExceptionListItemSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from '.'; + +export const updateExceptionListItemRoute = (router: IRouter): void => { + router.put( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_ITEM_URL, + validate: { + body: buildRouteValidation< + typeof updateExceptionListItemSchema, + UpdateExceptionListItemSchemaDecoded + >(updateExceptionListItemSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { + description, + id, + name, + meta, + type, + _tags, + comment, + entries, + item_id: itemId, + tags, + } = request.body; + const exceptionLists = getExceptionListClient(context); + const exceptionListItem = await exceptionLists.updateExceptionListItem({ + _tags, + comment, + description, + entries, + id, + itemId, + meta, + name, + namespaceType: 'single', // TODO: Bubble this up + tags, + type, + }); + if (exceptionListItem == null) { + return siemResponse.error({ + body: `list item id: "${id}" not found`, + statusCode: 404, + }); + } else { + const [validated, errors] = validate(exceptionListItem, exceptionListItemSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/update_exception_list_route.ts b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts new file mode 100644 index 0000000000000..a112c7422b952 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/update_exception_list_route.ts @@ -0,0 +1,83 @@ +/* + * 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 { IRouter } from 'kibana/server'; + +import { EXCEPTION_LIST_URL } from '../../common/constants'; +import { + buildRouteValidation, + buildSiemResponse, + transformError, + validate, +} from '../siem_server_deps'; +import { + UpdateExceptionListSchemaDecoded, + exceptionListSchema, + updateExceptionListSchema, +} from '../../common/schemas'; + +import { getExceptionListClient } from './utils'; + +export const updateExceptionListRoute = (router: IRouter): void => { + router.put( + { + options: { + tags: ['access:lists'], + }, + path: EXCEPTION_LIST_URL, + validate: { + body: buildRouteValidation< + typeof updateExceptionListSchema, + UpdateExceptionListSchemaDecoded + >(updateExceptionListSchema), + }, + }, + async (context, request, response) => { + const siemResponse = buildSiemResponse(response); + try { + const { _tags, tags, name, description, id, list_id: listId, meta, type } = request.body; + const exceptionLists = getExceptionListClient(context); + if (id == null && listId == null) { + return siemResponse.error({ + body: `either id or list_id need to be defined`, + statusCode: 404, + }); + } else { + const list = await exceptionLists.updateExceptionList({ + _tags, + description, + id, + listId, + meta, + name, + namespaceType: 'single', // TODO: Bubble this up + tags, + type, + }); + if (list == null) { + return siemResponse.error({ + body: `exception list id: "${id}" found found`, + statusCode: 404, + }); + } else { + const [validated, errors] = validate(list, exceptionListSchema); + if (errors != null) { + return siemResponse.error({ body: errors, statusCode: 500 }); + } else { + return response.ok({ body: validated ?? {} }); + } + } + } + } catch (err) { + const error = transformError(err); + return siemResponse.error({ + body: error.message, + statusCode: error.statusCode, + }); + } + } + ); +}; diff --git a/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list.ts b/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list.ts new file mode 100644 index 0000000000000..665a7540184a0 --- /dev/null +++ b/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export const getErrorMessageExceptionList = ({ + id, + listId, +}: { + id: string | undefined; + listId: string | undefined; +}): string => { + if (id != null) { + return `Exception list id: "${id}" does not exist`; + } else if (listId != null) { + return `Exception list list_id: "${listId}" does not exist`; + } else { + return 'Exception list does not exist'; + } +}; diff --git a/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list_item.ts b/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list_item.ts new file mode 100644 index 0000000000000..8e6730ef3db5c --- /dev/null +++ b/x-pack/plugins/lists/server/routes/utils/get_error_message_exception_list_item.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export const getErrorMessageExceptionListItem = ({ + id, + itemId, +}: { + id: string | undefined; + itemId: string | undefined; +}): string => { + if (id != null) { + return `Exception list item id: "${id}" does not exist`; + } else if (itemId != null) { + return `Exception list item list_id: "${itemId}" does not exist`; + } else { + return 'Exception list item does not exist'; + } +}; diff --git a/x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts b/x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts new file mode 100644 index 0000000000000..ba01ca617fb8b --- /dev/null +++ b/x-pack/plugins/lists/server/routes/utils/get_exception_list_client.ts @@ -0,0 +1,19 @@ +/* + * 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 { RequestHandlerContext } from 'kibana/server'; + +import { ErrorWithStatusCode } from '../../error_with_status_code'; +import { ExceptionListClient } from '../../services/exception_lists/exception_list_client'; + +export const getExceptionListClient = (context: RequestHandlerContext): ExceptionListClient => { + const exceptionLists = context.lists?.getExceptionListClient(); + if (exceptionLists == null) { + throw new ErrorWithStatusCode('Exception lists is not found as a plugin', 404); + } else { + return exceptionLists; + } +}; diff --git a/x-pack/plugins/lists/server/routes/utils/get_list_client.ts b/x-pack/plugins/lists/server/routes/utils/get_list_client.ts index a16163ec0fa3a..6ad69fd994bfd 100644 --- a/x-pack/plugins/lists/server/routes/utils/get_list_client.ts +++ b/x-pack/plugins/lists/server/routes/utils/get_list_client.ts @@ -6,7 +6,7 @@ import { RequestHandlerContext } from 'kibana/server'; -import { ListClient } from '../../services/lists/client'; +import { ListClient } from '../../services/lists/list_client'; import { ErrorWithStatusCode } from '../../error_with_status_code'; export const getListClient = (context: RequestHandlerContext): ListClient => { diff --git a/x-pack/plugins/lists/server/routes/utils/index.ts b/x-pack/plugins/lists/server/routes/utils/index.ts index a601bdfc003c5..90214872b68e3 100644 --- a/x-pack/plugins/lists/server/routes/utils/index.ts +++ b/x-pack/plugins/lists/server/routes/utils/index.ts @@ -3,4 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +export * from './get_error_message_exception_list_item'; +export * from './get_error_message_exception_list'; export * from './get_list_client'; +export * from './get_exception_list_client'; diff --git a/x-pack/plugins/lists/server/saved_objects/exception_list.ts b/x-pack/plugins/lists/server/saved_objects/exception_list.ts new file mode 100644 index 0000000000000..9e1a708e0c83b --- /dev/null +++ b/x-pack/plugins/lists/server/saved_objects/exception_list.ts @@ -0,0 +1,115 @@ +/* + * 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 { SavedObjectsType } from 'kibana/server'; + +export const exceptionListSavedObjectType = 'exception-list'; +export const exceptionListAgnosticSavedObjectType = 'exception-list-agnostic'; +export type SavedObjectType = 'exception-list' | 'exception-list-agnostic'; + +/** + * This is a super set of exception list and exception list items. The switch + * to determine if you are using an exception list vs. an exception list item + * is "list_type". If "list_type" is "list" then it is an exception list. If + * "list_type" is "item" then the type is an item. + */ +export const commonMapping: SavedObjectsType['mappings'] = { + properties: { + _tags: { + type: 'keyword', + }, + created_at: { + type: 'keyword', + }, + created_by: { + type: 'keyword', + }, + description: { + type: 'keyword', + }, + list_id: { + type: 'keyword', + }, + list_type: { + type: 'keyword', + }, + meta: { + type: 'keyword', + }, + name: { + type: 'keyword', + }, + tags: { + type: 'keyword', + }, + tie_breaker_id: { + type: 'keyword', + }, + type: { + type: 'keyword', + }, + updated_by: { + type: 'keyword', + }, + }, +}; + +export const exceptionListMapping: SavedObjectsType['mappings'] = { + properties: { + // There is nothing that is not also used within exceptionListItemMapping + // at this time but if there is it should go here + }, +}; + +export const exceptionListItemMapping: SavedObjectsType['mappings'] = { + properties: { + comment: { + // TODO: investigate what the deep mapping structure of this really is + type: 'keyword', + }, + entries: { + properties: { + field: { + type: 'keyword', + }, + match: { + type: 'keyword', + }, + match_any: { + type: 'keyword', + }, + operator: { + type: 'keyword', + }, + }, + }, + item_id: { + type: 'keyword', + }, + }, +}; + +const combinedMappings: SavedObjectsType['mappings'] = { + properties: { + ...commonMapping.properties, + ...exceptionListMapping.properties, + ...exceptionListItemMapping.properties, + }, +}; + +export const exceptionListType: SavedObjectsType = { + hidden: false, + mappings: combinedMappings, + name: exceptionListSavedObjectType, + namespaceType: 'single', +}; + +export const exceptionListAgnosticType: SavedObjectsType = { + hidden: false, + mappings: combinedMappings, + name: exceptionListAgnosticSavedObjectType, + namespaceType: 'agnostic', +}; diff --git a/x-pack/plugins/lists/server/saved_objects/index.ts b/x-pack/plugins/lists/server/saved_objects/index.ts new file mode 100644 index 0000000000000..b5077ae100cde --- /dev/null +++ b/x-pack/plugins/lists/server/saved_objects/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export * from './exception_list'; +export * from './init_saved_objects'; diff --git a/x-pack/plugins/lists/server/saved_objects/init_saved_objects.ts b/x-pack/plugins/lists/server/saved_objects/init_saved_objects.ts new file mode 100644 index 0000000000000..aab5ffd0e57fe --- /dev/null +++ b/x-pack/plugins/lists/server/saved_objects/init_saved_objects.ts @@ -0,0 +1,14 @@ +/* + * 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 { CoreSetup } from 'kibana/server'; + +import { exceptionListAgnosticType, exceptionListType } from './exception_list'; + +export const initSavedObjects = (savedObjects: CoreSetup['savedObjects']): void => { + savedObjects.registerType(exceptionListAgnosticType); + savedObjects.registerType(exceptionListType); +}; diff --git a/x-pack/plugins/lists/server/scripts/delete_all_exception_lists.sh b/x-pack/plugins/lists/server/scripts/delete_all_exception_lists.sh new file mode 100755 index 0000000000000..bb431800c56c3 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_all_exception_lists.sh @@ -0,0 +1,33 @@ +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_all_alerts.sh +# https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html +curl -s -k \ + -H "Content-Type: application/json" \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${ELASTICSEARCH_URL}/${KIBANA_INDEX}*/_delete_by_query \ + --data '{ + "query": { + "exists": { "field": "exception-list" } + } + }' \ + | jq . + +# Deletes all the agnostic namespace version +curl -s -k \ + -H "Content-Type: application/json" \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${ELASTICSEARCH_URL}/${KIBANA_INDEX}*/_delete_by_query \ + --data '{ + "query": { + "exists": { "field": "exception-list-agnostic" } + } + }' \ + | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_exception_list.sh b/x-pack/plugins/lists/server/scripts/delete_exception_list.sh new file mode 100755 index 0000000000000..fe2ca501b4416 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_exception_list.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_exception_list.sh ${list_id} +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists?list_id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_exception_list_by_id.sh b/x-pack/plugins/lists/server/scripts/delete_exception_list_by_id.sh new file mode 100755 index 0000000000000..a87881b385328 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_exception_list_by_id.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_exception_list_by_id.sh ${list_id} +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists?id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_exception_list_item.sh b/x-pack/plugins/lists/server/scripts/delete_exception_list_item.sh new file mode 100755 index 0000000000000..7e09452a23e11 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_exception_list_item.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_exception_list_item.sh ${item_id} +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?item_id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_exception_list_item_by_id.sh b/x-pack/plugins/lists/server/scripts/delete_exception_list_item_by_id.sh new file mode 100755 index 0000000000000..bbfbc3135ddb8 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/delete_exception_list_item_by_id.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./delete_exception_list_item_by_id.sh ${list_id} +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X DELETE ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/delete_list.sh b/x-pack/plugins/lists/server/scripts/delete_list.sh index 9934ce61c7107..ce9fdd6aa21d4 100755 --- a/x-pack/plugins/lists/server/scripts/delete_list.sh +++ b/x-pack/plugins/lists/server/scripts/delete_list.sh @@ -9,7 +9,7 @@ set -e ./check_env_variables.sh -# Example: ./delete_list_by_list_id.sh ${list_id} +# Example: ./delete_list.sh ${list_id} curl -s -k \ -H 'kbn-xsrf: 123' \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json new file mode 100644 index 0000000000000..520bc4ddf1e09 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list.json @@ -0,0 +1,8 @@ +{ + "list_id": "endpoint_list", + "_tags": ["endpoint", "process", "malware", "os:linux"], + "tags": ["user added string for a tag", "malware"], + "type": "endpoint", + "description": "This is a sample endpoint type exception", + "name": "Sample Endpoint Exception List" +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_auto_id.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_auto_id.json new file mode 100644 index 0000000000000..8c039bf6788ae --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_auto_id.json @@ -0,0 +1,5 @@ +{ + "description": "This is a sample exception list with auto created list_id since none was provided", + "name": "Sample Exception List without a whole lot going on", + "type": "endpoint" +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item.json new file mode 100644 index 0000000000000..2b97f37a7fa6b --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item.json @@ -0,0 +1,21 @@ +{ + "list_id": "endpoint_list", + "item_id": "endpoint_list_item", + "_tags": ["endpoint", "process", "malware", "os:linux"], + "tags": ["user added string for a tag", "malware"], + "type": "simple", + "description": "This is a sample endpoint type exception", + "name": "Sample Endpoint Exception List", + "entries": [ + { + "field": "actingProcess.file.signer", + "operator": "included", + "match": "Elastic, N.V." + }, + { + "field": "event.category", + "operator": "included", + "match_any": ["process", "malware"] + } + ] +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item_auto_id.json b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item_auto_id.json new file mode 100644 index 0000000000000..d68a26eb8ffe2 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/new/exception_list_item_auto_id.json @@ -0,0 +1,20 @@ +{ + "list_id": "endpoint_list", + "_tags": ["endpoint", "process", "malware", "os:linux"], + "tags": ["user added string for a tag", "malware"], + "type": "simple", + "description": "This is a sample endpoint type exception that has no item_id so it creates a new id each time", + "name": "Sample Endpoint Exception List", + "entries": [ + { + "field": "actingProcess.file.signer", + "operator": "included", + "match": "Elastic, N.V." + }, + { + "field": "event.category", + "operator": "included", + "match_any": ["process", "malware"] + } + ] +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json new file mode 100644 index 0000000000000..a7fbe1ea48c02 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update.json @@ -0,0 +1,8 @@ +{ + "list_id": "endpoint_list", + "_tags": ["endpoint", "process", "malware", "os:linux"], + "tags": ["user added string for a tag", "malware"], + "type": "endpoint", + "description": "Different description", + "name": "Sample Endpoint Exception List" +} diff --git a/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json new file mode 100644 index 0000000000000..a53079318edfa --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/exception_lists/updates/simple_update_item.json @@ -0,0 +1,15 @@ +{ + "item_id": "endpoint_list_item", + "_tags": ["endpoint", "process", "malware", "os:windows"], + "tags": ["user added string for a tag", "malware"], + "type": "simple", + "description": "This is a sample change here this list", + "name": "Sample Endpoint Exception List update change", + "entries": [ + { + "field": "event.category", + "operator": "included", + "match_any": ["process", "malware"] + } + ] +} diff --git a/x-pack/plugins/lists/server/scripts/find_exception_list_items.sh b/x-pack/plugins/lists/server/scripts/find_exception_list_items.sh new file mode 100755 index 0000000000000..85c5b0e518fab --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/find_exception_list_items.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +LIST_ID=${1:-endpoint_list} +# Example: ./find_exception_list_items.sh {list-id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items/_find?list_id=${LIST_ID} | jq . diff --git a/x-pack/plugins/lists/server/scripts/find_exception_lists.sh b/x-pack/plugins/lists/server/scripts/find_exception_lists.sh new file mode 100755 index 0000000000000..a1ee184b3e5bb --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/find_exception_lists.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./find_exception_lists.sh {list-id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/_find | jq . diff --git a/x-pack/plugins/lists/server/scripts/get_exception_list.sh b/x-pack/plugins/lists/server/scripts/get_exception_list.sh new file mode 100755 index 0000000000000..34e6de2576879 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/get_exception_list.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./get_exception_list.sh {id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists?list_id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/get_exception_list_by_id.sh b/x-pack/plugins/lists/server/scripts/get_exception_list_by_id.sh new file mode 100755 index 0000000000000..0420a1f702328 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/get_exception_list_by_id.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./get_exception_list_by_id.sh {id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists?id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/get_exception_list_item.sh b/x-pack/plugins/lists/server/scripts/get_exception_list_item.sh new file mode 100755 index 0000000000000..ac8337aab8368 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/get_exception_list_item.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./get_exception_list_item.sh {id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?item_id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/get_exception_list_item_by_id.sh b/x-pack/plugins/lists/server/scripts/get_exception_list_item_by_id.sh new file mode 100755 index 0000000000000..575a529c69906 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/get_exception_list_item_by_id.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Example: ./get_exception_list_item_by_id.sh {id} +curl -s -k \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items?id="$1" | jq . diff --git a/x-pack/plugins/lists/server/scripts/hard_reset.sh b/x-pack/plugins/lists/server/scripts/hard_reset.sh index 861928866369b..e00c3a9ace34f 100755 --- a/x-pack/plugins/lists/server/scripts/hard_reset.sh +++ b/x-pack/plugins/lists/server/scripts/hard_reset.sh @@ -12,3 +12,6 @@ set -e # re-create the list and list item indexes ./delete_list_index.sh ./post_list_index.sh + +# re-create the exception list and exception list items +./delete_all_exception_lists.sh diff --git a/x-pack/plugins/lists/server/scripts/post_exception_list.sh b/x-pack/plugins/lists/server/scripts/post_exception_list.sh new file mode 100755 index 0000000000000..84a775ffcf7f1 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/post_exception_list.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/new/exception_list.json}) + +# Example: ./post_exception_list.sh +# Example: ./post_exception_list.sh ./exception_lists/new/exception_list.json +for LIST in "${LISTS[@]}" +do { + [ -e "$LIST" ] || continue + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/exception_lists \ + -d @${LIST} \ + | jq .; +} & +done + +wait diff --git a/x-pack/plugins/lists/server/scripts/post_exception_list_item.sh b/x-pack/plugins/lists/server/scripts/post_exception_list_item.sh new file mode 100755 index 0000000000000..6cee54b1a6148 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/post_exception_list_item.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/new/exception_list_item.json}) + +# Example: ./post_exception_list_item.sh +# Example: ./post_exception_list_item.sh ./exception_lists/new/exception_list_item.json +for LIST in "${LISTS[@]}" +do { + [ -e "$LIST" ] || continue + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items \ + -d @${LIST} \ + | jq .; +} & +done + +wait diff --git a/x-pack/plugins/lists/server/scripts/update_exception_list.sh b/x-pack/plugins/lists/server/scripts/update_exception_list.sh new file mode 100755 index 0000000000000..d7523a0804a89 --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/update_exception_list.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/updates/simple_update.json}) + +# Example: ./update_exception_list.sh +# Example: ./update_exception_list.sh ./exception_lists/updates/simple_update.json +for LIST in "${LISTS[@]}" +do { + [ -e "$LIST" ] || continue + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X PUT ${KIBANA_URL}${SPACE_URL}/api/exception_lists \ + -d @${LIST} \ + | jq .; +} & +done + +wait diff --git a/x-pack/plugins/lists/server/scripts/update_exception_list_item.sh b/x-pack/plugins/lists/server/scripts/update_exception_list_item.sh new file mode 100755 index 0000000000000..029bfcdabee3e --- /dev/null +++ b/x-pack/plugins/lists/server/scripts/update_exception_list_item.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# 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. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +LISTS=(${@:-./exception_lists/updates/simple_update_item.json}) + +# Example: ./update_exception_list_item.sh +# Example: ./update_exception_list_item.sh ./exception_lists/updates/simple_update_item.json +for LIST in "${LISTS[@]}" +do { + [ -e "$LIST" ] || continue + curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X PUT ${KIBANA_URL}${SPACE_URL}/api/exception_lists/items \ + -d @${LIST} \ + | jq .; +} & +done + +wait diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts new file mode 100644 index 0000000000000..7ba832e72bb8e --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list.ts @@ -0,0 +1,72 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; +import uuid from 'uuid'; + +import { + Description, + ExceptionListSchema, + ExceptionListSoSchema, + ExceptionListType, + ListId, + MetaOrUndefined, + Name, + Tags, + _Tags, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils'; +import { NamespaceType } from './types'; + +interface CreateExceptionListOptions { + _tags: _Tags; + listId: ListId; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; + name: Name; + description: Description; + meta: MetaOrUndefined; + user: string; + tags: Tags; + tieBreaker?: string; + type: ExceptionListType; +} + +export const createExceptionList = async ({ + _tags, + listId, + savedObjectsClient, + namespaceType, + name, + description, + meta, + user, + tags, + tieBreaker, + type, +}: CreateExceptionListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const dateNow = new Date().toISOString(); + const savedObject = await savedObjectsClient.create(savedObjectType, { + _tags, + comment: undefined, + created_at: dateNow, + created_by: user, + description, + entries: undefined, + item_id: undefined, + list_id: listId, + list_type: 'list', + meta, + name, + tags, + tie_breaker_id: tieBreaker ?? uuid.v4(), + type, + updated_by: user, + }); + return transformSavedObjectToExceptionList({ savedObject }); +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts new file mode 100644 index 0000000000000..4a6dc1da97854 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/create_exception_list_item.ts @@ -0,0 +1,81 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; +import uuid from 'uuid'; + +import { + CommentOrUndefined, + Description, + EntriesArray, + ExceptionListItemSchema, + ExceptionListSoSchema, + ExceptionListType, + ItemId, + ListId, + MetaOrUndefined, + Name, + Tags, + _Tags, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectToExceptionListItem } from './utils'; +import { NamespaceType } from './types'; + +interface CreateExceptionListItemOptions { + _tags: _Tags; + comment: CommentOrUndefined; + listId: ListId; + itemId: ItemId; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; + name: Name; + description: Description; + entries: EntriesArray; + meta: MetaOrUndefined; + user: string; + tags: Tags; + tieBreaker?: string; + type: ExceptionListType; +} + +export const createExceptionListItem = async ({ + _tags, + comment, + entries, + itemId, + listId, + savedObjectsClient, + namespaceType, + name, + description, + meta, + user, + tags, + tieBreaker, + type, +}: CreateExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const dateNow = new Date().toISOString(); + const savedObject = await savedObjectsClient.create(savedObjectType, { + _tags, + comment, + created_at: dateNow, + created_by: user, + description, + entries, + item_id: itemId, + list_id: listId, + list_type: 'item', + meta, + name, + tags, + tie_breaker_id: tieBreaker ?? uuid.v4(), + type, + updated_by: user, + }); + return transformSavedObjectToExceptionListItem({ savedObject }); +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts new file mode 100644 index 0000000000000..6904438c8d275 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list.ts @@ -0,0 +1,42 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; + +import { ExceptionListSchema, IdOrUndefined, ListIdOrUndefined } from '../../../common/schemas'; + +import { getSavedObjectType } from './utils'; +import { NamespaceType } from './types'; +import { getExceptionList } from './get_exception_list'; +import { deleteExceptionListItemByList } from './delete_exception_list_items_by_list'; + +interface DeleteExceptionListOptions { + listId: ListIdOrUndefined; + id: IdOrUndefined; + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; +} + +export const deleteExceptionList = async ({ + listId, + id, + namespaceType, + savedObjectsClient, +}: DeleteExceptionListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionList = await getExceptionList({ id, listId, namespaceType, savedObjectsClient }); + if (exceptionList == null) { + return null; + } else { + await deleteExceptionListItemByList({ + listId: exceptionList.list_id, + namespaceType, + savedObjectsClient, + }); + await savedObjectsClient.delete(savedObjectType, exceptionList.id); + return exceptionList; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts new file mode 100644 index 0000000000000..3b2d991281cd6 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_item.ts @@ -0,0 +1,41 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; + +import { ExceptionListItemSchema, IdOrUndefined, ItemIdOrUndefined } from '../../../common/schemas'; + +import { getSavedObjectType } from './utils'; +import { NamespaceType } from './types'; +import { getExceptionListItem } from './get_exception_list_item'; + +interface DeleteExceptionListItemOptions { + itemId: ItemIdOrUndefined; + id: IdOrUndefined; + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; +} + +export const deleteExceptionListItem = async ({ + itemId, + id, + namespaceType, + savedObjectsClient, +}: DeleteExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionListItem = await getExceptionListItem({ + id, + itemId, + namespaceType, + savedObjectsClient, + }); + if (exceptionListItem == null) { + return null; + } else { + await savedObjectsClient.delete(savedObjectType, exceptionListItem.id); + return exceptionListItem; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts new file mode 100644 index 0000000000000..8031f085c1635 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/delete_exception_list_items_by_list.ts @@ -0,0 +1,89 @@ +/* + * 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 { SavedObjectsClientContract } from '../../../../../../src/core/server/'; +import { ListId } from '../../../common/schemas'; + +import { findExceptionListItem } from './find_exception_list_item'; +import { NamespaceType } from './types'; +import { getSavedObjectType } from './utils'; + +const PER_PAGE = 100; + +interface DeleteExceptionListItemByListOptions { + listId: ListId; + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; +} + +export const deleteExceptionListItemByList = async ({ + listId, + savedObjectsClient, + namespaceType, +}: DeleteExceptionListItemByListOptions): Promise => { + const ids = await getExceptionListItemIds({ listId, namespaceType, savedObjectsClient }); + await deleteFoundExceptionListItems({ ids, namespaceType, savedObjectsClient }); +}; + +export const getExceptionListItemIds = async ({ + listId, + savedObjectsClient, + namespaceType, +}: DeleteExceptionListItemByListOptions): Promise => { + let page = 1; + let ids: string[] = []; + let foundExceptionListItems = await findExceptionListItem({ + filter: undefined, + listId, + namespaceType, + page, + perPage: PER_PAGE, + savedObjectsClient, + sortField: 'tie_breaker_id', + sortOrder: 'desc', + }); + while (foundExceptionListItems != null && foundExceptionListItems.data.length > 0) { + ids = [...ids, ...foundExceptionListItems.data.map(exceptionListItem => exceptionListItem.id)]; + page += 1; + foundExceptionListItems = await findExceptionListItem({ + filter: undefined, + listId, + namespaceType, + page, + perPage: PER_PAGE, + savedObjectsClient, + sortField: 'tie_breaker_id', + sortOrder: 'desc', + }); + } + return ids; +}; + +/** + * NOTE: This is slow and terrible as we are deleting everything one at a time. + * TODO: Replace this with a bulk call or a delete by query would be more useful + */ +export const deleteFoundExceptionListItems = async ({ + ids, + savedObjectsClient, + namespaceType, +}: { + ids: string[]; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; +}): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + ids.forEach(async id => { + try { + await savedObjectsClient.delete(savedObjectType, id); + } catch (err) { + // This can happen from race conditions or networking issues so deleting the id's + // like this is considered "best effort" and it is possible to get dangling pieces + // of data sitting around in which case the user has to manually clean up the data + // I am very hopeful this does not happen often or at all. + } + }); +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts new file mode 100644 index 0000000000000..6e71ed1b3e59d --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts @@ -0,0 +1,250 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; + +import { + ExceptionListSchema, + FoundExceptionListItemSchema, + FoundExceptionListSchema, +} from '../../../common/schemas'; + +import { + ConstructorOptions, + CreateExceptionListItemOptions, + CreateExceptionListOptions, + DeleteExceptionListItemOptions, + DeleteExceptionListOptions, + FindExceptionListItemOptions, + FindExceptionListOptions, + GetExceptionListItemOptions, + GetExceptionListOptions, + UpdateExceptionListItemOptions, + UpdateExceptionListOptions, +} from './exception_list_client_types'; +import { getExceptionList } from './get_exception_list'; +import { createExceptionList } from './create_exception_list'; +import { getExceptionListItem } from './get_exception_list_item'; +import { createExceptionListItem } from './create_exception_list_item'; +import { updateExceptionList } from './update_exception_list'; +import { updateExceptionListItem } from './update_exception_list_item'; +import { deleteExceptionList } from './delete_exception_list'; +import { deleteExceptionListItem } from './delete_exception_list_item'; +import { findExceptionListItem } from './find_exception_list_item'; +import { findExceptionList } from './find_exception_list'; + +export class ExceptionListClient { + private readonly user: string; + + private readonly savedObjectsClient: SavedObjectsClientContract; + + constructor({ user, savedObjectsClient }: ConstructorOptions) { + this.user = user; + this.savedObjectsClient = savedObjectsClient; + } + + public getExceptionList = async ({ + listId, + id, + namespaceType, + }: GetExceptionListOptions): Promise => { + const { savedObjectsClient } = this; + return getExceptionList({ id, listId, namespaceType, savedObjectsClient }); + }; + + public getExceptionListItem = async ({ + itemId, + id, + namespaceType, + }: GetExceptionListItemOptions): Promise => { + const { savedObjectsClient } = this; + return getExceptionListItem({ id, itemId, namespaceType, savedObjectsClient }); + }; + + public createExceptionList = async ({ + _tags, + description, + listId, + meta, + name, + namespaceType, + tags, + type, + }: CreateExceptionListOptions): Promise => { + const { savedObjectsClient, user } = this; + return createExceptionList({ + _tags, + description, + listId, + meta, + name, + namespaceType, + savedObjectsClient, + tags, + type, + user, + }); + }; + + public updateExceptionList = async ({ + _tags, + id, + description, + listId, + meta, + name, + namespaceType, + tags, + type, + }: UpdateExceptionListOptions): Promise => { + const { savedObjectsClient, user } = this; + return updateExceptionList({ + _tags, + description, + id, + listId, + meta, + name, + namespaceType, + savedObjectsClient, + tags, + type, + user, + }); + }; + + public deleteExceptionList = async ({ + id, + listId, + namespaceType, + }: DeleteExceptionListOptions): Promise => { + const { savedObjectsClient } = this; + return deleteExceptionList({ + id, + listId, + namespaceType, + savedObjectsClient, + }); + }; + + public createExceptionListItem = async ({ + _tags, + comment, + description, + entries, + itemId, + listId, + meta, + name, + namespaceType, + tags, + type, + }: CreateExceptionListItemOptions): Promise => { + const { savedObjectsClient, user } = this; + return createExceptionListItem({ + _tags, + comment, + description, + entries, + itemId, + listId, + meta, + name, + namespaceType, + savedObjectsClient, + tags, + type, + user, + }); + }; + + public updateExceptionListItem = async ({ + _tags, + comment, + description, + entries, + id, + itemId, + meta, + name, + namespaceType, + tags, + type, + }: UpdateExceptionListItemOptions): Promise => { + const { savedObjectsClient, user } = this; + return updateExceptionListItem({ + _tags, + comment, + description, + entries, + id, + itemId, + meta, + name, + namespaceType, + savedObjectsClient, + tags, + type, + user, + }); + }; + + public deleteExceptionListItem = async ({ + id, + itemId, + namespaceType, + }: DeleteExceptionListItemOptions): Promise => { + const { savedObjectsClient } = this; + return deleteExceptionListItem({ + id, + itemId, + namespaceType, + savedObjectsClient, + }); + }; + + public findExceptionListItem = async ({ + listId, + filter, + perPage, + page, + sortField, + sortOrder, + namespaceType, + }: FindExceptionListItemOptions): Promise => { + const { savedObjectsClient } = this; + return findExceptionListItem({ + filter, + listId, + namespaceType, + page, + perPage, + savedObjectsClient, + sortField, + sortOrder, + }); + }; + + public findExceptionList = async ({ + filter, + perPage, + page, + sortField, + sortOrder, + namespaceType, + }: FindExceptionListOptions): Promise => { + const { savedObjectsClient } = this; + return findExceptionList({ + filter, + namespaceType, + page, + perPage, + savedObjectsClient, + sortField, + sortOrder, + }); + }; +} diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts new file mode 100644 index 0000000000000..cecd6bf3397a7 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client_types.ts @@ -0,0 +1,130 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; + +import { + CommentOrUndefined, + Description, + DescriptionOrUndefined, + EntriesArray, + EntriesArrayOrUndefined, + ExceptionListType, + ExceptionListTypeOrUndefined, + IdOrUndefined, + ItemId, + ItemIdOrUndefined, + ListId, + ListIdOrUndefined, + MetaOrUndefined, + Name, + NameOrUndefined, + Tags, + TagsOrUndefined, + _Tags, + _TagsOrUndefined, +} from '../../../common/schemas'; + +import { NamespaceType } from './types'; + +export interface ConstructorOptions { + user: string; + savedObjectsClient: SavedObjectsClientContract; +} + +export interface GetExceptionListOptions { + listId: ListIdOrUndefined; + id: IdOrUndefined; + namespaceType: NamespaceType; +} + +export interface CreateExceptionListOptions { + _tags: _Tags; + listId: ListId; + namespaceType: NamespaceType; + name: Name; + description: Description; + meta: MetaOrUndefined; + tags: Tags; + type: ExceptionListType; +} + +export interface UpdateExceptionListOptions { + _tags: _TagsOrUndefined; + id: IdOrUndefined; + listId: ListIdOrUndefined; + namespaceType: NamespaceType; + name: NameOrUndefined; + description: DescriptionOrUndefined; + meta: MetaOrUndefined; + tags: TagsOrUndefined; + type: ExceptionListTypeOrUndefined; +} + +export interface DeleteExceptionListOptions { + id: IdOrUndefined; + listId: ListIdOrUndefined; + namespaceType: NamespaceType; +} + +export interface DeleteExceptionListItemOptions { + id: IdOrUndefined; + itemId: ItemIdOrUndefined; + namespaceType: NamespaceType; +} + +export interface GetExceptionListItemOptions { + itemId: ItemIdOrUndefined; + id: IdOrUndefined; + namespaceType: NamespaceType; +} + +export interface CreateExceptionListItemOptions { + _tags: _Tags; + comment: CommentOrUndefined; + entries: EntriesArray; + itemId: ItemId; + listId: ListId; + namespaceType: NamespaceType; + name: Name; + description: Description; + meta: MetaOrUndefined; + tags: Tags; + type: ExceptionListType; +} + +export interface UpdateExceptionListItemOptions { + _tags: _TagsOrUndefined; + comment: CommentOrUndefined; + entries: EntriesArrayOrUndefined; + id: IdOrUndefined; + itemId: ItemIdOrUndefined; + namespaceType: NamespaceType; + name: NameOrUndefined; + description: DescriptionOrUndefined; + meta: MetaOrUndefined; + tags: TagsOrUndefined; + type: ExceptionListTypeOrUndefined; +} + +export interface FindExceptionListItemOptions { + listId: ListId; + namespaceType: NamespaceType; + filter: string | undefined; + perPage: number | undefined; + page: number | undefined; + sortField: string | undefined; + sortOrder: string | undefined; +} + +export interface FindExceptionListOptions { + namespaceType: NamespaceType; + filter: string | undefined; + perPage: number | undefined; + page: number | undefined; + sortField: string | undefined; + sortOrder: string | undefined; +} diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts new file mode 100644 index 0000000000000..539dda673208b --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list.ts @@ -0,0 +1,58 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; + +import { ExceptionListSoSchema, FoundExceptionListSchema } from '../../../common/schemas'; +import { SavedObjectType } from '../../saved_objects'; + +import { getSavedObjectType, transformSavedObjectsToFounExceptionList } from './utils'; +import { NamespaceType } from './types'; + +interface FindExceptionListOptions { + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; + filter: string | undefined; + perPage: number | undefined; + page: number | undefined; + sortField: string | undefined; + sortOrder: string | undefined; +} + +export const findExceptionList = async ({ + namespaceType, + savedObjectsClient, + filter, + page, + perPage, + sortField, + sortOrder, +}: FindExceptionListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const savedObjectsFindResponse = await savedObjectsClient.find({ + filter: getExceptionListFilter({ filter, savedObjectType }), + page, + perPage, + sortField, + sortOrder, + type: savedObjectType, + }); + return transformSavedObjectsToFounExceptionList({ savedObjectsFindResponse }); +}; + +export const getExceptionListFilter = ({ + filter, + savedObjectType, +}: { + filter: string | undefined; + savedObjectType: SavedObjectType; +}): string => { + if (filter == null) { + return `${savedObjectType}.attributes.list_type: list`; + } else { + return `${savedObjectType}.attributes.list_type: list AND ${filter}`; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts new file mode 100644 index 0000000000000..d635cafbd3b1b --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/find_exception_list_item.ts @@ -0,0 +1,77 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; + +import { + ExceptionListSoSchema, + FoundExceptionListItemSchema, + ListId, +} from '../../../common/schemas'; +import { SavedObjectType } from '../../saved_objects'; + +import { getSavedObjectType, transformSavedObjectsToFounExceptionListItem } from './utils'; +import { NamespaceType } from './types'; +import { getExceptionList } from './get_exception_list'; + +interface FindExceptionListItemOptions { + listId: ListId; + namespaceType: NamespaceType; + savedObjectsClient: SavedObjectsClientContract; + filter: string | undefined; + perPage: number | undefined; + page: number | undefined; + sortField: string | undefined; + sortOrder: string | undefined; +} + +export const findExceptionListItem = async ({ + listId, + namespaceType, + savedObjectsClient, + filter, + page, + perPage, + sortField, + sortOrder, +}: FindExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionList = await getExceptionList({ + id: undefined, + listId, + namespaceType, + savedObjectsClient, + }); + if (exceptionList == null) { + return null; + } else { + const savedObjectsFindResponse = await savedObjectsClient.find({ + filter: getExceptionListItemFilter({ filter, listId, savedObjectType }), + page, + perPage, + sortField, + sortOrder, + type: savedObjectType, + }); + return transformSavedObjectsToFounExceptionListItem({ savedObjectsFindResponse }); + } +}; + +export const getExceptionListItemFilter = ({ + filter, + listId, + savedObjectType, +}: { + listId: ListId; + filter: string | undefined; + savedObjectType: SavedObjectType; +}): string => { + if (filter == null) { + return `${savedObjectType}.attributes.list_type: item AND ${savedObjectType}.attributes.list_id: ${listId}`; + } else { + return `${savedObjectType}.attributes.list_type: item AND ${savedObjectType}.attributes.list_id: ${listId} AND ${filter}`; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts new file mode 100644 index 0000000000000..8b28443b4e30c --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list.ts @@ -0,0 +1,64 @@ +/* + * 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 { + SavedObjectsClientContract, + SavedObjectsErrorHelpers, +} from '../../../../../../src/core/server/'; +import { + ExceptionListSchema, + ExceptionListSoSchema, + IdOrUndefined, + ListIdOrUndefined, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectToExceptionList } from './utils'; +import { NamespaceType } from './types'; + +interface GetExceptionListOptions { + id: IdOrUndefined; + listId: ListIdOrUndefined; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; +} + +export const getExceptionList = async ({ + id, + listId, + savedObjectsClient, + namespaceType, +}: GetExceptionListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + if (id != null) { + try { + const savedObject = await savedObjectsClient.get(savedObjectType, id); + return transformSavedObjectToExceptionList({ savedObject }); + } catch (err) { + if (SavedObjectsErrorHelpers.isNotFoundError(err)) { + return null; + } else { + throw err; + } + } + } else if (listId != null) { + const savedObject = await savedObjectsClient.find({ + filter: `${savedObjectType}.attributes.list_type: list`, + perPage: 1, + search: listId, + searchFields: ['list_id'], + sortField: 'tie_breaker_id', + sortOrder: 'desc', + type: savedObjectType, + }); + if (savedObject.saved_objects[0] != null) { + return transformSavedObjectToExceptionList({ savedObject: savedObject.saved_objects[0] }); + } else { + return null; + } + } else { + return null; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.ts new file mode 100644 index 0000000000000..7ef3e4af3d604 --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/get_exception_list_item.ts @@ -0,0 +1,66 @@ +/* + * 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 { + SavedObjectsClientContract, + SavedObjectsErrorHelpers, +} from '../../../../../../src/core/server/'; +import { + ExceptionListItemSchema, + ExceptionListSoSchema, + IdOrUndefined, + ItemIdOrUndefined, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectToExceptionListItem } from './utils'; +import { NamespaceType } from './types'; + +interface GetExceptionListItemOptions { + id: IdOrUndefined; + itemId: ItemIdOrUndefined; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; +} + +export const getExceptionListItem = async ({ + id, + itemId, + savedObjectsClient, + namespaceType, +}: GetExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + if (id != null) { + try { + const savedObject = await savedObjectsClient.get(savedObjectType, id); + return transformSavedObjectToExceptionListItem({ savedObject }); + } catch (err) { + if (SavedObjectsErrorHelpers.isNotFoundError(err)) { + return null; + } else { + throw err; + } + } + } else if (itemId != null) { + const savedObject = await savedObjectsClient.find({ + filter: `${savedObjectType}.attributes.list_type: item`, + perPage: 1, + search: itemId, + searchFields: ['item_id'], + sortField: 'tie_breaker_id', + sortOrder: 'desc', + type: savedObjectType, + }); + if (savedObject.saved_objects[0] != null) { + return transformSavedObjectToExceptionListItem({ + savedObject: savedObject.saved_objects[0], + }); + } else { + return null; + } + } else { + return null; + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/index.ts b/x-pack/plugins/lists/server/services/exception_lists/index.ts new file mode 100644 index 0000000000000..a66f00819605b --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ + +export * from './create_exception_list_item'; +export * from './create_exception_list'; +export * from './delete_exception_list_item'; +export * from './delete_exception_list'; +export * from './find_exception_list'; +export * from './find_exception_list_item'; +export * from './get_exception_list_item'; +export * from './get_exception_list'; +export * from './update_exception_list_item'; +export * from './update_exception_list'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/types.ts b/x-pack/plugins/lists/server/services/exception_lists/types.ts new file mode 100644 index 0000000000000..dbb188bc2754a --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/types.ts @@ -0,0 +1,6 @@ +/* + * 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. + */ +export type NamespaceType = 'agnostic' | 'single'; diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts new file mode 100644 index 0000000000000..6c5ccb5e1f2fd --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list.ts @@ -0,0 +1,74 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; + +import { + DescriptionOrUndefined, + ExceptionListSchema, + ExceptionListSoSchema, + ExceptionListTypeOrUndefined, + IdOrUndefined, + ListIdOrUndefined, + MetaOrUndefined, + NameOrUndefined, + TagsOrUndefined, + _TagsOrUndefined, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectUpdateToExceptionList } from './utils'; +import { NamespaceType } from './types'; +import { getExceptionList } from './get_exception_list'; + +interface UpdateExceptionListOptions { + id: IdOrUndefined; + _tags: _TagsOrUndefined; + name: NameOrUndefined; + description: DescriptionOrUndefined; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; + listId: ListIdOrUndefined; + meta: MetaOrUndefined; + user: string; + tags: TagsOrUndefined; + tieBreaker?: string; + type: ExceptionListTypeOrUndefined; +} + +export const updateExceptionList = async ({ + _tags, + id, + savedObjectsClient, + namespaceType, + name, + description, + listId, + meta, + user, + tags, + type, +}: UpdateExceptionListOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionList = await getExceptionList({ id, listId, namespaceType, savedObjectsClient }); + if (exceptionList == null) { + return null; + } else { + const savedObject = await savedObjectsClient.update( + savedObjectType, + exceptionList.id, + { + _tags, + description, + meta, + name, + tags, + type, + updated_by: user, + } + ); + return transformSavedObjectUpdateToExceptionList({ exceptionList, savedObject }); + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts new file mode 100644 index 0000000000000..4e955d4281c4d --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/update_exception_list_item.ts @@ -0,0 +1,87 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; + +import { + CommentOrUndefined, + DescriptionOrUndefined, + EntriesArrayOrUndefined, + ExceptionListItemSchema, + ExceptionListSoSchema, + ExceptionListTypeOrUndefined, + IdOrUndefined, + ItemIdOrUndefined, + MetaOrUndefined, + NameOrUndefined, + TagsOrUndefined, + _TagsOrUndefined, +} from '../../../common/schemas'; + +import { getSavedObjectType, transformSavedObjectUpdateToExceptionListItem } from './utils'; +import { NamespaceType } from './types'; +import { getExceptionListItem } from './get_exception_list_item'; + +interface UpdateExceptionListItemOptions { + id: IdOrUndefined; + comment: CommentOrUndefined; + _tags: _TagsOrUndefined; + name: NameOrUndefined; + description: DescriptionOrUndefined; + entries: EntriesArrayOrUndefined; + savedObjectsClient: SavedObjectsClientContract; + namespaceType: NamespaceType; + itemId: ItemIdOrUndefined; + meta: MetaOrUndefined; + user: string; + tags: TagsOrUndefined; + tieBreaker?: string; + type: ExceptionListTypeOrUndefined; +} + +export const updateExceptionListItem = async ({ + _tags, + comment, + entries, + id, + savedObjectsClient, + namespaceType, + name, + description, + itemId, + meta, + user, + tags, + type, +}: UpdateExceptionListItemOptions): Promise => { + const savedObjectType = getSavedObjectType({ namespaceType }); + const exceptionListItem = await getExceptionListItem({ + id, + itemId, + namespaceType, + savedObjectsClient, + }); + if (exceptionListItem == null) { + return null; + } else { + const savedObject = await savedObjectsClient.update( + savedObjectType, + exceptionListItem.id, + { + _tags, + comment, + description, + entries, + meta, + name, + tags, + type, + updated_by: user, + } + ); + return transformSavedObjectUpdateToExceptionListItem({ exceptionListItem, savedObject }); + } +}; diff --git a/x-pack/plugins/lists/server/services/exception_lists/utils.ts b/x-pack/plugins/lists/server/services/exception_lists/utils.ts new file mode 100644 index 0000000000000..505ebc43afc5a --- /dev/null +++ b/x-pack/plugins/lists/server/services/exception_lists/utils.ts @@ -0,0 +1,235 @@ +/* + * 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 { SavedObject, SavedObjectsFindResponse, SavedObjectsUpdateResponse } from 'kibana/server'; + +import { + ExceptionListItemSchema, + ExceptionListSchema, + ExceptionListSoSchema, + FoundExceptionListItemSchema, + FoundExceptionListSchema, +} from '../../../common/schemas'; +import { + SavedObjectType, + exceptionListAgnosticSavedObjectType, + exceptionListSavedObjectType, +} from '../../saved_objects'; + +import { NamespaceType } from './types'; + +export const getSavedObjectType = ({ + namespaceType, +}: { + namespaceType: NamespaceType; +}): SavedObjectType => { + if (namespaceType === 'agnostic') { + return exceptionListAgnosticSavedObjectType; + } else { + return exceptionListSavedObjectType; + } +}; + +export const transformSavedObjectToExceptionList = ({ + savedObject, +}: { + savedObject: SavedObject; +}): ExceptionListSchema => { + const dateNow = new Date().toISOString(); + const { + attributes: { + _tags, + created_at, + created_by, + description, + list_id, + meta, + name, + tags, + tie_breaker_id, + type, + updated_by, + }, + id, + updated_at: updatedAt, + } = savedObject; + + // TODO: Change this to do a decode and throw if the saved object is not as expected. + // TODO: Do a throw if after the decode this is not the correct "list_type: list" + return { + _tags, + created_at, + created_by, + description, + id, + list_id, + meta, + name, + tags, + tie_breaker_id, + type, + updated_at: updatedAt ?? dateNow, + updated_by, + }; +}; + +export const transformSavedObjectUpdateToExceptionList = ({ + exceptionList, + savedObject, +}: { + exceptionList: ExceptionListSchema; + savedObject: SavedObjectsUpdateResponse; +}): ExceptionListSchema => { + const dateNow = new Date().toISOString(); + const { + attributes: { _tags, description, meta, name, tags, type, updated_by: updatedBy }, + id, + updated_at: updatedAt, + } = savedObject; + + // TODO: Change this to do a decode and throw if the saved object is not as expected. + // TODO: Do a throw if after the decode this is not the correct "list_type: list" + return { + _tags: _tags ?? exceptionList._tags, + created_at: exceptionList.created_at, + created_by: exceptionList.created_by, + description: description ?? exceptionList.description, + id, + list_id: exceptionList.list_id, + meta: meta ?? exceptionList.meta, + name: name ?? exceptionList.name, + tags: tags ?? exceptionList.tags, + tie_breaker_id: exceptionList.tie_breaker_id, + type: type ?? exceptionList.type, + updated_at: updatedAt ?? dateNow, + updated_by: updatedBy ?? exceptionList.updated_by, + }; +}; + +export const transformSavedObjectToExceptionListItem = ({ + savedObject, +}: { + savedObject: SavedObject; +}): ExceptionListItemSchema => { + const dateNow = new Date().toISOString(); + const { + attributes: { + _tags, + comment, + created_at, + created_by, + description, + entries, + item_id: itemId, + list_id, + meta, + name, + tags, + tie_breaker_id, + type, + updated_by, + }, + id, + updated_at: updatedAt, + } = savedObject; + // TODO: Change this to do a decode and throw if the saved object is not as expected. + // TODO: Do a throw if after the decode this is not the correct "list_type: item" + // TODO: Do a throw if item_id or entries is not defined. + return { + _tags, + comment, + created_at, + created_by, + description, + entries: entries ?? [], + id, + item_id: itemId ?? '(unknown)', + list_id, + meta, + name, + tags, + tie_breaker_id, + type, + updated_at: updatedAt ?? dateNow, + updated_by, + }; +}; + +export const transformSavedObjectUpdateToExceptionListItem = ({ + exceptionListItem, + savedObject, +}: { + exceptionListItem: ExceptionListItemSchema; + savedObject: SavedObjectsUpdateResponse; +}): ExceptionListItemSchema => { + const dateNow = new Date().toISOString(); + const { + attributes: { + _tags, + comment, + description, + entries, + meta, + name, + tags, + type, + updated_by: updatedBy, + }, + id, + updated_at: updatedAt, + } = savedObject; + + // TODO: Change this to do a decode and throw if the saved object is not as expected. + // TODO: Do a throw if after the decode this is not the correct "list_type: list" + return { + _tags: _tags ?? exceptionListItem._tags, + comment: comment ?? exceptionListItem.comment, + created_at: exceptionListItem.created_at, + created_by: exceptionListItem.created_by, + description: description ?? exceptionListItem.description, + entries: entries ?? exceptionListItem.entries, + id, + item_id: exceptionListItem.item_id, + list_id: exceptionListItem.list_id, + meta: meta ?? exceptionListItem.meta, + name: name ?? exceptionListItem.name, + tags: tags ?? exceptionListItem.tags, + tie_breaker_id: exceptionListItem.tie_breaker_id, + type: type ?? exceptionListItem.type, + updated_at: updatedAt ?? dateNow, + updated_by: updatedBy ?? exceptionListItem.updated_by, + }; +}; + +export const transformSavedObjectsToFounExceptionListItem = ({ + savedObjectsFindResponse, +}: { + savedObjectsFindResponse: SavedObjectsFindResponse; +}): FoundExceptionListItemSchema => { + return { + data: savedObjectsFindResponse.saved_objects.map(savedObject => + transformSavedObjectToExceptionListItem({ savedObject }) + ), + page: savedObjectsFindResponse.page, + per_page: savedObjectsFindResponse.per_page, + total: savedObjectsFindResponse.total, + }; +}; + +export const transformSavedObjectsToFounExceptionList = ({ + savedObjectsFindResponse, +}: { + savedObjectsFindResponse: SavedObjectsFindResponse; +}): FoundExceptionListSchema => { + return { + data: savedObjectsFindResponse.saved_objects.map(savedObject => + transformSavedObjectToExceptionList({ savedObject }) + ), + page: savedObjectsFindResponse.page, + per_page: savedObjectsFindResponse.per_page, + total: savedObjectsFindResponse.total, + }; +}; diff --git a/x-pack/plugins/lists/server/services/lists/client.ts b/x-pack/plugins/lists/server/services/lists/list_client.ts similarity index 99% rename from x-pack/plugins/lists/server/services/lists/client.ts rename to x-pack/plugins/lists/server/services/lists/list_client.ts index ba22bf72cc18c..cba48115c746c 100644 --- a/x-pack/plugins/lists/server/services/lists/client.ts +++ b/x-pack/plugins/lists/server/services/lists/list_client.ts @@ -59,7 +59,7 @@ import { ImportListItemsToStreamOptions, UpdateListItemOptions, UpdateListOptions, -} from './client_types'; +} from './list_client_types'; export class ListClient { private readonly spaceId: string; diff --git a/x-pack/plugins/lists/server/services/lists/client_types.ts b/x-pack/plugins/lists/server/services/lists/list_client_types.ts similarity index 100% rename from x-pack/plugins/lists/server/services/lists/client_types.ts rename to x-pack/plugins/lists/server/services/lists/list_client_types.ts diff --git a/x-pack/plugins/lists/server/services/utils/index.ts b/x-pack/plugins/lists/server/services/utils/index.ts index 8a44b5ab607bf..e6365e689f761 100644 --- a/x-pack/plugins/lists/server/services/utils/index.ts +++ b/x-pack/plugins/lists/server/services/utils/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './derive_type_from_es_type'; export * from './get_query_filter_from_type_value'; export * from './transform_elastic_to_list_item'; export * from './transform_list_item_to_elastic_query'; -export * from './derive_type_from_es_type'; diff --git a/x-pack/plugins/lists/server/siem_server_deps.ts b/x-pack/plugins/lists/server/siem_server_deps.ts index 6231b2e014111..81ebe9f17b06f 100644 --- a/x-pack/plugins/lists/server/siem_server_deps.ts +++ b/x-pack/plugins/lists/server/siem_server_deps.ts @@ -6,27 +6,16 @@ export { transformError, + deleteTemplate, + deletePolicy, + deleteAllIndex, + setPolicy, + setTemplate, buildSiemResponse, -} from '../../siem/server/lib/detection_engine/routes/utils'; // eslint-disable-line @kbn/eslint/no-restricted-paths -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { deleteTemplate } from '../../siem/server/lib/detection_engine/index/delete_template'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { deletePolicy } from '../../siem/server/lib/detection_engine/index/delete_policy'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { deleteAllIndex } from '../../siem/server/lib/detection_engine/index/delete_all_index'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { setPolicy } from '../../siem/server/lib/detection_engine/index/set_policy'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { setTemplate } from '../../siem/server/lib/detection_engine/index/set_template'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { getTemplateExists } from '../../siem/server/lib/detection_engine/index/get_template_exists'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { getPolicyExists } from '../../siem/server/lib/detection_engine/index/get_policy_exists'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { createBootstrapIndex } from '../../siem/server/lib/detection_engine/index/create_bootstrap_index'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { getIndexExists } from '../../siem/server/lib/detection_engine/index/get_index_exists'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { buildRouteValidation } from '../../siem/server/utils/build_validation/route_validation'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -export { validate } from '../../siem/server/lib/detection_engine/routes/rules/validate'; + getTemplateExists, + getPolicyExists, + createBootstrapIndex, + getIndexExists, + buildRouteValidation, + validate, +} from '../../siem/server'; diff --git a/x-pack/plugins/lists/server/types.ts b/x-pack/plugins/lists/server/types.ts index d7c3208e556fa..6808aaa04ab7a 100644 --- a/x-pack/plugins/lists/server/types.ts +++ b/x-pack/plugins/lists/server/types.ts @@ -4,12 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { APICaller, IContextProvider, RequestHandler } from 'kibana/server'; +import { + APICaller, + IContextProvider, + RequestHandler, + SavedObjectsClientContract, +} from 'kibana/server'; import { SecurityPluginSetup } from '../../security/server'; import { SpacesPluginSetup } from '../../spaces/server'; -import { ListClient } from './services/lists/client'; +import { ListClient } from './services/lists/list_client'; +import { ExceptionListClient } from './services/exception_lists/exception_list_client'; export type ContextProvider = IContextProvider, 'lists'>; export type ListsPluginStart = void; @@ -23,14 +29,25 @@ export type GetListClientType = ( spaceId: string, user: string ) => ListClient; + +export type GetExceptionListClientType = ( + savedObjectsClient: SavedObjectsClientContract, + user: string +) => ExceptionListClient; + export interface ListPluginSetup { + getExceptionListClient: GetExceptionListClientType; getListClient: GetListClientType; } -export type ContextProviderReturn = Promise<{ getListClient: () => ListClient }>; +export type ContextProviderReturn = Promise<{ + getListClient: () => ListClient; + getExceptionListClient: () => ExceptionListClient; +}>; declare module 'src/core/server' { interface RequestHandlerContext { lists?: { + getExceptionListClient: () => ExceptionListClient; getListClient: () => ListClient; }; } diff --git a/x-pack/plugins/logstash/public/application/breadcrumbs.js b/x-pack/plugins/logstash/public/application/breadcrumbs.js index 322b9860b3785..4ef259b84e24f 100644 --- a/x-pack/plugins/logstash/public/application/breadcrumbs.js +++ b/x-pack/plugins/logstash/public/application/breadcrumbs.js @@ -12,7 +12,7 @@ export function getPipelineListBreadcrumbs() { text: i18n.translate('xpack.logstash.pipelines.listBreadcrumb', { defaultMessage: 'Pipelines', }), - href: '#/management/logstash/pipelines', + href: '#/management/ingest/pipelines', }, ]; } diff --git a/x-pack/plugins/logstash/public/plugin.ts b/x-pack/plugins/logstash/public/plugin.ts index 7fbed5b3b8602..7d4afc0a4ea13 100644 --- a/x-pack/plugins/logstash/public/plugin.ts +++ b/x-pack/plugins/logstash/public/plugin.ts @@ -14,8 +14,8 @@ import { HomePublicPluginSetup, FeatureCatalogueCategory, } from '../../../../src/plugins/home/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { LicensingPluginSetup } from '../../licensing/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; // @ts-ignore import { LogstashLicenseService } from './services'; @@ -34,26 +34,23 @@ export class LogstashPlugin implements Plugin { const logstashLicense$ = plugins.licensing.license$.pipe( map(license => new LogstashLicenseService(license)) ); - const section = plugins.management.sections.register({ - id: 'logstash', - title: 'Logstash', - order: 30, - euiIconType: 'logoLogstash', - }); - const managementApp = section.registerApp({ - id: 'pipelines', - title: i18n.translate('xpack.logstash.managementSection.pipelinesTitle', { - defaultMessage: 'Pipelines', - }), - order: 10, - mount: async params => { - const [coreStart] = await core.getStartServices(); - const { renderApp } = await import('./application'); - const isMonitoringEnabled = 'monitoring' in plugins; - return renderApp(coreStart, params, isMonitoringEnabled, logstashLicense$); - }, - }); + const managementApp = plugins.management.sections + .getSection(ManagementSectionId.Ingest) + .registerApp({ + id: 'pipelines', + title: i18n.translate('xpack.logstash.managementSection.pipelinesTitle', { + defaultMessage: 'Logstash Pipelines', + }), + order: 1, + mount: async params => { + const [coreStart] = await core.getStartServices(); + const { renderApp } = await import('./application'); + const isMonitoringEnabled = 'monitoring' in plugins; + + return renderApp(coreStart, params, isMonitoringEnabled, logstashLicense$); + }, + }); this.licenseSubscription = logstashLicense$.subscribe((license: any) => { if (license.enableLinks) { @@ -74,7 +71,7 @@ export class LogstashPlugin implements Plugin { defaultMessage: 'Create, delete, update, and clone data ingestion pipelines.', }), icon: 'pipelineApp', - path: '/app/kibana#/management/logstash/pipelines', + path: '/app/kibana#/management/ingest/pipelines', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/maps/public/actions/map_actions.js b/x-pack/plugins/maps/public/actions/map_actions.js index 5aa7c74a7b30b..1dfdfc3a73d8a 100644 --- a/x-pack/plugins/maps/public/actions/map_actions.js +++ b/x-pack/plugins/maps/public/actions/map_actions.js @@ -111,14 +111,36 @@ function getLayerById(layerId, state) { }); } -async function syncDataForAllLayers(dispatch, getState, dataFilters) { - const state = getState(); - const layerList = getLayerList(state); - const syncs = layerList.map(layer => { - const loadingFunctions = getLayerLoadingCallbacks(dispatch, getState, layer.getId()); - return layer.syncData({ ...loadingFunctions, dataFilters }); - }); - await Promise.all(syncs); +function syncDataForAllLayers() { + return async (dispatch, getState) => { + const syncPromises = getLayerList(getState()).map(async layer => { + return dispatch(syncDataForLayer(layer)); + }); + await Promise.all(syncPromises); + }; +} + +function syncDataForLayer(layer) { + return async (dispatch, getState) => { + const dataFilters = getDataFilters(getState()); + if (!layer.isVisible() || !layer.showAtZoomLevel(dataFilters.zoom)) { + return; + } + + await layer.syncData({ + ...getLayerLoadingCallbacks(dispatch, getState, layer.getId()), + dataFilters, + }); + }; +} + +function syncDataForLayerId(layerId) { + return async (dispatch, getState) => { + const layer = getLayerById(layerId, getState()); + if (layer) { + dispatch(syncDataForLayer(layer)); + } + }; } export function cancelAllInFlightRequests() { @@ -192,7 +214,7 @@ export function rollbackToTrackedLayerStateForSelectedLayer() { // syncDataForLayer may not trigger endDataLoad if no re-fetch is required dispatch(updateStyleMeta(layerId)); - dispatch(syncDataForLayer(layerId)); + dispatch(syncDataForLayerId(layerId)); }; } @@ -252,7 +274,7 @@ export function addLayer(layerDescriptor) { type: ADD_LAYER, layer: layerDescriptor, }); - dispatch(syncDataForLayer(layerDescriptor.id)); + dispatch(syncDataForLayerId(layerDescriptor.id)); }; } @@ -333,7 +355,7 @@ export function setLayerVisibility(layerId, makeVisible) { visibility: makeVisible, }); if (makeVisible) { - dispatch(syncDataForLayer(layerId)); + dispatch(syncDataForLayer(layer)); } }; } @@ -466,8 +488,7 @@ export function mapExtentChanged(newMapConstants) { ...newMapConstants, }, }); - const newDataFilters = { ...dataFilters, ...newMapConstants }; - await syncDataForAllLayers(dispatch, getState, newDataFilters); + await dispatch(syncDataForAllLayers()); }; } @@ -751,7 +772,7 @@ export function updateSourceProp(layerId, propName, value, newLayerType) { dispatch(updateLayerType(layerId, newLayerType)); } await dispatch(clearMissingStyleProperties(layerId)); - dispatch(syncDataForLayer(layerId)); + dispatch(syncDataForLayerId(layerId)); }; } @@ -771,20 +792,6 @@ function updateLayerType(layerId, newLayerType) { }; } -export function syncDataForLayer(layerId) { - return async (dispatch, getState) => { - const targetLayer = getLayerById(layerId, getState()); - if (targetLayer) { - const dataFilters = getDataFilters(getState()); - const loadingFunctions = getLayerLoadingCallbacks(dispatch, getState, layerId); - await targetLayer.syncData({ - ...loadingFunctions, - dataFilters, - }); - } - }; -} - export function updateLayerLabel(id, newLabel) { return { type: UPDATE_LAYER_PROP, @@ -830,7 +837,7 @@ export function setLayerQuery(id, query) { newValue: query, }); - dispatch(syncDataForLayer(id)); + dispatch(syncDataForLayerId(id)); }; } @@ -895,8 +902,7 @@ export function setQuery({ query, timeFilters, filters = [], refresh = false }) filters, }); - const dataFilters = getDataFilters(getState()); - await syncDataForAllLayers(dispatch, getState, dataFilters); + await dispatch(syncDataForAllLayers()); }; } @@ -909,13 +915,12 @@ export function setRefreshConfig({ isPaused, interval }) { } export function triggerRefreshTimer() { - return async (dispatch, getState) => { + return async dispatch => { dispatch({ type: TRIGGER_REFRESH_TIMER, }); - const dataFilters = getDataFilters(getState()); - await syncDataForAllLayers(dispatch, getState, dataFilters); + await dispatch(syncDataForAllLayers()); }; } @@ -956,7 +961,7 @@ export function updateLayerStyle(layerId, styleDescriptor) { dispatch(updateStyleMeta(layerId)); // Style update may require re-fetch, for example ES search may need to retrieve field used for dynamic styling - dispatch(syncDataForLayer(layerId)); + dispatch(syncDataForLayerId(layerId)); }; } @@ -994,12 +999,12 @@ export function setJoinsForLayer(layer, joins) { return async dispatch => { await dispatch({ type: SET_JOINS, - layer: layer, - joins: joins, + layer, + joins, }); await dispatch(clearMissingStyleProperties(layer.getId())); - dispatch(syncDataForLayer(layer.getId())); + dispatch(syncDataForLayerId(layer.getId())); }; } diff --git a/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js b/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js index 69f5033e3af0f..5108e5cd3e6a3 100644 --- a/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/tile_layer/tile_layer.js @@ -25,9 +25,6 @@ export class TileLayer extends AbstractLayer { } async syncData({ startLoading, stopLoading, onLoadError, dataFilters }) { - if (!this.isVisible() || !this.showAtZoomLevel(dataFilters.zoom)) { - return; - } const sourceDataRequest = this.getSourceDataRequest(); if (sourceDataRequest) { //data is immmutable diff --git a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx index bb4fbe9d01b60..f8f78f6e97829 100644 --- a/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/tiled_vector_layer/tiled_vector_layer.tsx @@ -78,10 +78,6 @@ export class TiledVectorLayer extends VectorLayer { } async syncData(syncContext: SyncContext) { - if (!this.isVisible() || !this.showAtZoomLevel(syncContext.dataFilters.zoom)) { - return; - } - await this._syncSourceStyleMeta(syncContext, this._source, this._style); await this._syncSourceFormatters(syncContext, this._source, this._style); await this._syncMVTUrlTemplate(syncContext); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js index ccbc8a1c21324..d32593f73c46c 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.js @@ -571,10 +571,6 @@ export class VectorLayer extends AbstractLayer { // Given 2 above, which source/style to use can not be pulled from data request state. // Therefore, source and style are provided as arugments and must be used instead of calling getSource or getCurrentStyle. async _syncData(syncContext, source, style) { - if (!this.isVisible() || !this.showAtZoomLevel(syncContext.dataFilters.zoom)) { - return; - } - await this._syncSourceStyleMeta(syncContext, source, style); await this._syncSourceFormatters(syncContext, source, style); const sourceResult = await this._syncSource(syncContext, source, style); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js index fe1ff58922162..4ffd0d93fd22a 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js +++ b/x-pack/plugins/maps/public/classes/layers/vector_tile_layer/vector_tile_layer.js @@ -45,10 +45,6 @@ export class VectorTileLayer extends TileLayer { } async syncData({ startLoading, stopLoading, onLoadError, dataFilters }) { - if (!this.isVisible() || !this.showAtZoomLevel(dataFilters.zoom)) { - return; - } - const nextMeta = { tileLayerId: this.getSource().getTileLayerId() }; const canSkipSync = this._canSkipSync({ prevDataRequest: this.getSourceDataRequest(), diff --git a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx index 2d6505f5ce1f7..126fd25a536f6 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx @@ -184,7 +184,7 @@ export const DatavisualizerSelector: FC = () => { footer={ = ({ /> } description="" - href={`${basePath.get()}/app/kibana#/management/elasticsearch/index_management/indices/filter/${index}`} + href={`${basePath.get()}/app/kibana#/management/data/index_management/indices/filter/${index}`} /> diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js index 2a65ee06f2c2c..307fa79f5dea2 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js @@ -167,7 +167,7 @@ class CreateWatchService { saveWatch(watchModel) .then(() => { this.status.watch = this.STATUS.SAVED; - this.config.watcherEditURL = `${basePath.get()}/app/kibana#/management/elasticsearch/watcher/watches/watch/${id}/edit?_g=()`; + this.config.watcherEditURL = `${basePath.get()}/app/kibana#/management/insightsAndAlerting/watcher/watches/watch/${id}/edit?_g=()`; resolve({ id, url: this.config.watcherEditURL, diff --git a/x-pack/plugins/ml/public/application/management/index.ts b/x-pack/plugins/ml/public/application/management/index.ts index 6bc5c9b15074f..f15cdb12afb21 100644 --- a/x-pack/plugins/ml/public/application/management/index.ts +++ b/x-pack/plugins/ml/public/application/management/index.ts @@ -16,7 +16,11 @@ import { take } from 'rxjs/operators'; import { CoreSetup } from 'kibana/public'; import { MlStartDependencies, MlSetupDependencies } from '../../plugin'; -import { PLUGIN_ID, PLUGIN_ICON } from '../../../common/constants/app'; +import { + ManagementAppMountParams, + ManagementSectionId, +} from '../../../../../../src/plugins/management/public'; +import { PLUGIN_ID } from '../../../common/constants/app'; import { MINIMUM_FULL_LICENSE } from '../../../common/license'; export function initManagementSection( @@ -30,22 +34,13 @@ export function initManagementSection( management !== undefined && license.check(PLUGIN_ID, MINIMUM_FULL_LICENSE).state === 'valid' ) { - const mlSection = management.sections.register({ - id: PLUGIN_ID, - title: i18n.translate('xpack.ml.management.mlTitle', { - defaultMessage: 'Machine Learning', - }), - order: 100, - icon: PLUGIN_ICON, - }); - - mlSection.registerApp({ + management.sections.getSection(ManagementSectionId.InsightsAndAlerting).registerApp({ id: 'jobsListLink', title: i18n.translate('xpack.ml.management.jobsListTitle', { - defaultMessage: 'Jobs list', + defaultMessage: 'Machine Learning Jobs', }), - order: 10, - async mount(params) { + order: 2, + async mount(params: ManagementAppMountParams) { const { mountApp } = await import('./jobs_list'); return mountApp(core, params); }, diff --git a/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx b/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx index 3e4e9cfbd2b66..87a7156b6f52e 100644 --- a/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/sidebar.tsx @@ -42,7 +42,7 @@ export const OverviewSideBar: FC = ({ createAnomalyDetectionJobDisabled } const { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = docLinks; const docsLink = `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/xpack-ml.html`; - const transformsLink = `${basePath.get()}/app/kibana#/management/elasticsearch/transform`; + const transformsLink = `${basePath.get()}/app/kibana#/management/data/transform`; return ( diff --git a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js index feda891c1ce29..69d7727f9a20a 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js +++ b/x-pack/plugins/monitoring/public/components/cluster/listing/listing.js @@ -288,7 +288,7 @@ const handleClickIncompatibleLicense = (scope, clusterName) => { }; const handleClickInvalidLicense = (scope, clusterName) => { - const licensingPath = `${Legacy.shims.getBasePath()}/app/kibana#/management/elasticsearch/license_management/home`; + const licensingPath = `${Legacy.shims.getBasePath()}/app/kibana#/management/stack/license_management/home`; licenseWarning(scope, { title: toMountPoint( diff --git a/x-pack/plugins/monitoring/public/components/license/index.js b/x-pack/plugins/monitoring/public/components/license/index.js index 085cc9082cf53..e8ea1f8df227a 100644 --- a/x-pack/plugins/monitoring/public/components/license/index.js +++ b/x-pack/plugins/monitoring/public/components/license/index.js @@ -169,7 +169,7 @@ const LicenseUpdateInfoForRemote = ({ isPrimaryCluster }) => { export function License(props) { const { status, type, isExpired, expiryDate } = props; - const licenseManagement = `${Legacy.shims.getBasePath()}/app/kibana#/management/elasticsearch/license_management`; + const licenseManagement = `${Legacy.shims.getBasePath()}/app/kibana#/management/stack/license_management`; return ( diff --git a/x-pack/plugins/remote_clusters/public/application/constants/paths.ts b/x-pack/plugins/remote_clusters/public/application/constants/paths.ts index 23fe6758542c9..770447ce33f93 100644 --- a/x-pack/plugins/remote_clusters/public/application/constants/paths.ts +++ b/x-pack/plugins/remote_clusters/public/application/constants/paths.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CRUD_APP_BASE_PATH: string = '/management/elasticsearch/remote_clusters'; +export const CRUD_APP_BASE_PATH: string = '/management/data/remote_clusters'; diff --git a/x-pack/plugins/remote_clusters/public/plugin.ts b/x-pack/plugins/remote_clusters/public/plugin.ts index 22f98e94748d8..fde8ffa511319 100644 --- a/x-pack/plugins/remote_clusters/public/plugin.ts +++ b/x-pack/plugins/remote_clusters/public/plugin.ts @@ -6,6 +6,8 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin, CoreStart, PluginInitializerContext } from 'kibana/public'; + +import { ManagementSectionId } from '../../../../src/plugins/management/public'; import { init as initBreadcrumbs } from './application/services/breadcrumb'; import { init as initDocumentation } from './application/services/documentation'; import { init as initHttp } from './application/services/http'; @@ -31,13 +33,14 @@ export class RemoteClustersUIPlugin } = this.initializerContext.config.get(); if (isRemoteClustersUiEnabled) { - const esSection = management.sections.getSection('elasticsearch'); + const esSection = management.sections.getSection(ManagementSectionId.Data); - esSection!.registerApp({ + esSection.registerApp({ id: 'remote_clusters', title: i18n.translate('xpack.remoteClusters.appTitle', { defaultMessage: 'Remote Clusters', }), + order: 7, mount: async ({ element, setBreadcrumbs }) => { const [core] = await getStartServices(); const { diff --git a/x-pack/plugins/reporting/public/plugin.tsx b/x-pack/plugins/reporting/public/plugin.tsx index 66366cc0b520d..f600b1ebbb96c 100644 --- a/x-pack/plugins/reporting/public/plugin.tsx +++ b/x-pack/plugins/reporting/public/plugin.tsx @@ -17,9 +17,9 @@ import { Plugin, PluginInitializerContext, } from 'src/core/public'; -import { ManagementSetup } from 'src/plugins/management/public'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; import { JobId, JobStatusBuckets } from '../'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { CONTEXT_MENU_TRIGGER } from '../../../../src/plugins/embeddable/public'; import { FeatureCatalogueCategory, @@ -117,10 +117,10 @@ export class ReportingPublicPlugin implements Plugin { category: FeatureCatalogueCategory.ADMIN, }); - management.sections.getSection('kibana')!.registerApp({ + management.sections.getSection(ManagementSectionId.InsightsAndAlerting).registerApp({ id: 'reporting', title: this.title, - order: 15, + order: 1, mount: async params => { const [start] = await getStartServices(); params.setBreadcrumbs([{ text: this.breadcrumbText }]); diff --git a/x-pack/plugins/rollup/public/crud_app/constants/paths.js b/x-pack/plugins/rollup/public/crud_app/constants/paths.js index 83a7ca6bc5967..44829f38e79cd 100644 --- a/x-pack/plugins/rollup/public/crud_app/constants/paths.js +++ b/x-pack/plugins/rollup/public/crud_app/constants/paths.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CRUD_APP_BASE_PATH = '/management/elasticsearch/rollup_jobs'; +export const CRUD_APP_BASE_PATH = '/management/data/rollup_jobs'; diff --git a/x-pack/plugins/rollup/public/plugin.ts b/x-pack/plugins/rollup/public/plugin.ts index 0e0333cf30f17..b2e793d7e75e9 100644 --- a/x-pack/plugins/rollup/public/plugin.ts +++ b/x-pack/plugins/rollup/public/plugin.ts @@ -18,7 +18,7 @@ import { } from '../../../../src/plugins/home/public'; // @ts-ignore import { CRUD_APP_BASE_PATH } from './crud_app/constants'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { IndexManagementPluginSetup } from '../../index_management/public'; import { IndexPatternManagementSetup } from '../../../../src/plugins/index_pattern_management/public'; // @ts-ignore @@ -77,26 +77,23 @@ export class RollupPlugin implements Plugin { }); } - const esSection = management.sections.getSection('elasticsearch'); - if (esSection) { - esSection.registerApp({ - id: 'rollup_jobs', - title: i18n.translate('xpack.rollupJobs.appTitle', { defaultMessage: 'Rollup Jobs' }), - order: 5, - async mount(params) { - params.setBreadcrumbs([ - { - text: i18n.translate('xpack.rollupJobs.breadcrumbsTitle', { - defaultMessage: 'Rollup Jobs', - }), - }, - ]); - const { renderApp } = await import('./application'); + management.sections.getSection(ManagementSectionId.Data).registerApp({ + id: 'rollup_jobs', + title: i18n.translate('xpack.rollupJobs.appTitle', { defaultMessage: 'Rollup Jobs' }), + order: 4, + async mount(params) { + params.setBreadcrumbs([ + { + text: i18n.translate('xpack.rollupJobs.breadcrumbsTitle', { + defaultMessage: 'Rollup Jobs', + }), + }, + ]); + const { renderApp } = await import('./application'); - return renderApp(core, params); - }, - }); - } + return renderApp(core, params); + }, + }); } start(core: CoreStart) { diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index ee9a1844c7468..575bc9bf9dff1 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -14,6 +14,7 @@ import { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; import { CoreSetup, + ICustomClusterClient, Plugin, Logger, KibanaRequest, @@ -42,11 +43,18 @@ import { mergeCapabilitiesWithFields } from './lib/merge_capabilities_with_field interface RollupContext { client: IScopedClusterClient; } +async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { + const [core] = await getStartServices(); + // Extend the elasticsearchJs client with additional endpoints. + const esClientConfig = { plugins: [elasticsearchJsPlugin] }; + return core.elasticsearch.legacy.createClient('rollup', esClientConfig); +} export class RollupPlugin implements Plugin { private readonly logger: Logger; private readonly globalConfig$: Observable; private readonly license: License; + private rollupEsClient?: ICustomClusterClient; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); @@ -55,7 +63,7 @@ export class RollupPlugin implements Plugin { } public setup( - { http, uiSettings, elasticsearch }: CoreSetup, + { http, uiSettings, getStartServices }: CoreSetup, { licensing, indexManagement, visTypeTimeseries, usageCollection }: Dependencies ) { this.license.setup( @@ -72,12 +80,10 @@ export class RollupPlugin implements Plugin { } ); - // Extend the elasticsearchJs client with additional endpoints. - const esClientConfig = { plugins: [elasticsearchJsPlugin] }; - const rollupEsClient = elasticsearch.createClient('rollup', esClientConfig); - http.registerRouteHandlerContext('rollup', (context, request) => { + http.registerRouteHandlerContext('rollup', async (context, request) => { + this.rollupEsClient = this.rollupEsClient ?? (await getCustomEsClient(getStartServices)); return { - client: rollupEsClient.asScoped(request), + client: this.rollupEsClient.asScoped(request), }; }); @@ -116,7 +122,12 @@ export class RollupPlugin implements Plugin { const callWithRequestFactoryShim = ( elasticsearchServiceShim: CallWithRequestFactoryShim, request: KibanaRequest - ): APICaller => rollupEsClient.asScoped(request).callAsCurrentUser; + ): APICaller => { + return async (...args: Parameters) => { + this.rollupEsClient = this.rollupEsClient ?? (await getCustomEsClient(getStartServices)); + return await this.rollupEsClient.asScoped(request).callAsCurrentUser(...args); + }; + }; const { addSearchStrategy } = visTypeTimeseries; registerRollupSearchStrategy(callWithRequestFactoryShim, addSearchStrategy); @@ -140,5 +151,10 @@ export class RollupPlugin implements Plugin { } start() {} - stop() {} + + stop() { + if (this.rollupEsClient) { + this.rollupEsClient.close(); + } + } } diff --git a/x-pack/plugins/security/public/management/management_service.test.ts b/x-pack/plugins/security/public/management/management_service.test.ts index 53c12ad7ab12c..466e1aa9b3c68 100644 --- a/x-pack/plugins/security/public/management/management_service.test.ts +++ b/x-pack/plugins/security/public/management/management_service.test.ts @@ -27,9 +27,8 @@ describe('ManagementService', () => { const mockSection = { registerApp: jest.fn() }; const managementSetup = { sections: { - getSection: jest.fn(), + getSection: jest.fn().mockReturnValue(mockSection), getAllSections: jest.fn(), - register: jest.fn().mockReturnValue(mockSection), }, }; @@ -42,14 +41,6 @@ describe('ManagementService', () => { management: managementSetup, }); - expect(managementSetup.sections.register).toHaveBeenCalledTimes(1); - expect(managementSetup.sections.register).toHaveBeenCalledWith({ - id: 'security', - title: 'Security', - order: 100, - euiIconType: 'securityApp', - }); - expect(mockSection.registerApp).toHaveBeenCalledTimes(4); expect(mockSection.registerApp).toHaveBeenCalledWith({ id: 'users', @@ -96,9 +87,8 @@ describe('ManagementService', () => { authc: securityMock.createSetup().authc, management: { sections: { - getSection: jest.fn(), + getSection: jest.fn().mockReturnValue({ registerApp: jest.fn() }), getAllSections: jest.fn(), - register: jest.fn().mockReturnValue({ registerApp: jest.fn() }), }, }, }); diff --git a/x-pack/plugins/security/public/management/management_service.ts b/x-pack/plugins/security/public/management/management_service.ts index 7c4c470730ffe..2dc3ecbd9f3a2 100644 --- a/x-pack/plugins/security/public/management/management_service.ts +++ b/x-pack/plugins/security/public/management/management_service.ts @@ -5,12 +5,12 @@ */ import { Subscription } from 'rxjs'; -import { i18n } from '@kbn/i18n'; import { StartServicesAccessor, FatalErrorsSetup } from 'src/core/public'; import { ManagementApp, ManagementSetup, ManagementStart, + ManagementSectionId, } from '../../../../../src/plugins/management/public'; import { SecurityLicense } from '../../common/licensing'; import { AuthenticationServiceSetup } from '../authentication'; @@ -39,14 +39,7 @@ export class ManagementService { setup({ getStartServices, management, authc, license, fatalErrors }: SetupParams) { this.license = license; - const securitySection = management.sections.register({ - id: 'security', - title: i18n.translate('xpack.security.management.securityTitle', { - defaultMessage: 'Security', - }), - order: 100, - euiIconType: 'securityApp', - }); + const securitySection = management.sections.getSection(ManagementSectionId.Security); securitySection.registerApp(usersManagementApp.create({ authc, getStartServices })); securitySection.registerApp( @@ -58,7 +51,7 @@ export class ManagementService { start({ management }: StartParams) { this.licenseFeaturesSubscription = this.license.features$.subscribe(async features => { - const securitySection = management.sections.getSection('security')!; + const securitySection = management.sections.getSection(ManagementSectionId.Security); const securityManagementAppsStatuses: Array<[ManagementApp, boolean]> = [ [securitySection.getApp(usersManagementApp.id)!, features.showLinks], diff --git a/x-pack/plugins/siem/server/utils/build_validation/exact_check.test.ts b/x-pack/plugins/siem/common/exact_check.test.ts similarity index 98% rename from x-pack/plugins/siem/server/utils/build_validation/exact_check.test.ts rename to x-pack/plugins/siem/common/exact_check.test.ts index 1e70deaeed438..a5701603de0d6 100644 --- a/x-pack/plugins/siem/server/utils/build_validation/exact_check.test.ts +++ b/x-pack/plugins/siem/common/exact_check.test.ts @@ -8,8 +8,8 @@ import * as t from 'io-ts'; import { left, right, Either } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { foldLeftRight, getPaths } from './__mocks__/utils'; import { exactCheck, findDifferencesRecursive } from './exact_check'; +import { foldLeftRight, getPaths } from './test_utils'; describe('exact_check', () => { test('it returns an error if given extra object properties', () => { diff --git a/x-pack/plugins/siem/server/utils/build_validation/exact_check.ts b/x-pack/plugins/siem/common/exact_check.ts similarity index 100% rename from x-pack/plugins/siem/server/utils/build_validation/exact_check.ts rename to x-pack/plugins/siem/common/exact_check.ts diff --git a/x-pack/plugins/siem/server/utils/build_validation/format_errors.test.ts b/x-pack/plugins/siem/common/format_errors.test.ts similarity index 100% rename from x-pack/plugins/siem/server/utils/build_validation/format_errors.test.ts rename to x-pack/plugins/siem/common/format_errors.test.ts diff --git a/x-pack/plugins/siem/server/utils/build_validation/format_errors.ts b/x-pack/plugins/siem/common/format_errors.ts similarity index 100% rename from x-pack/plugins/siem/server/utils/build_validation/format_errors.ts rename to x-pack/plugins/siem/common/format_errors.ts diff --git a/x-pack/plugins/siem/server/utils/build_validation/__mocks__/utils.ts b/x-pack/plugins/siem/common/test_utils.ts similarity index 95% rename from x-pack/plugins/siem/server/utils/build_validation/__mocks__/utils.ts rename to x-pack/plugins/siem/common/test_utils.ts index 578972dda5aef..29b0119dcbeb7 100644 --- a/x-pack/plugins/siem/server/utils/build_validation/__mocks__/utils.ts +++ b/x-pack/plugins/siem/common/test_utils.ts @@ -7,7 +7,7 @@ import * as t from 'io-ts'; import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { formatErrors } from '../format_errors'; +import { formatErrors } from './format_errors'; interface Message { errors: t.Errors; diff --git a/x-pack/plugins/siem/public/alerts/components/rules/select_rule_type/index.tsx b/x-pack/plugins/siem/public/alerts/components/rules/select_rule_type/index.tsx index 58112732bea3b..4a21eb0fbcf23 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/select_rule_type/index.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/select_rule_type/index.tsx @@ -77,7 +77,7 @@ export const SelectRuleType: React.FC = ({ const setQuery = useCallback(() => setType('query'), [setType]); const mlCardDisabled = isReadOnly || !hasValidLicense || !isMlAdmin; const licensingUrl = useKibana().services.application.getUrlForApp('kibana', { - path: '#/management/elasticsearch/license_management', + path: '#/management/stack/license_management', }); return ( diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap index 113612200d367..87cf9fb18adf3 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap +++ b/x-pack/plugins/siem/public/common/components/ml_popover/__snapshots__/upgrade_contents.test.tsx.snap @@ -50,7 +50,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` grow={false} > diff --git a/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx b/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx index 5a483e0d5b876..eda6b0f28499c 100644 --- a/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx +++ b/x-pack/plugins/siem/public/common/components/ml_popover/upgrade_contents.tsx @@ -59,7 +59,7 @@ export const UpgradeContentsComponent = () => ( diff --git a/x-pack/plugins/siem/public/graphql/types.ts b/x-pack/plugins/siem/public/graphql/types.ts index 3436ee84a2f30..c3493c580fa22 100644 --- a/x-pack/plugins/siem/public/graphql/types.ts +++ b/x-pack/plugins/siem/public/graphql/types.ts @@ -2541,6 +2541,140 @@ export interface DeleteTimelineMutationArgs { // Documents // ==================================================== +export namespace GetLastEventTimeQuery { + export type Variables = { + sourceId: string; + indexKey: LastEventIndexKey; + details: LastTimeDetails; + defaultIndex: string[]; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + LastEventTime: LastEventTime; + }; + + export type LastEventTime = { + __typename?: 'LastEventTimeData'; + + lastSeen: Maybe; + }; +} + +export namespace GetMatrixHistogramQuery { + export type Variables = { + defaultIndex: string[]; + filterQuery?: Maybe; + histogramType: HistogramType; + inspect: boolean; + sourceId: string; + stackByField: string; + timerange: TimerangeInput; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + MatrixHistogram: MatrixHistogram; + }; + + export type MatrixHistogram = { + __typename?: 'MatrixHistogramOverTimeData'; + + matrixHistogramData: MatrixHistogramData[]; + + totalCount: number; + + inspect: Maybe; + }; + + export type MatrixHistogramData = { + __typename?: 'MatrixOverTimeHistogramData'; + + x: Maybe; + + y: Maybe; + + g: Maybe; + }; + + export type Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; + }; +} + +export namespace SourceQuery { + export type Variables = { + sourceId?: Maybe; + defaultIndex: string[]; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + status: Status; + }; + + export type Status = { + __typename?: 'SourceStatus'; + + indicesExist: boolean; + + indexFields: IndexFields[]; + }; + + export type IndexFields = { + __typename?: 'IndexField'; + + category: string; + + description: Maybe; + + example: Maybe; + + indexes: (Maybe)[]; + + name: string; + + searchable: boolean; + + type: string; + + aggregatable: boolean; + + format: Maybe; + }; +} + export namespace GetAuthenticationsQuery { export type Variables = { sourceId: string; @@ -2680,35 +2814,6 @@ export namespace GetAuthenticationsQuery { }; } -export namespace GetLastEventTimeQuery { - export type Variables = { - sourceId: string; - indexKey: LastEventIndexKey; - details: LastTimeDetails; - defaultIndex: string[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - LastEventTime: LastEventTime; - }; - - export type LastEventTime = { - __typename?: 'LastEventTimeData'; - - lastSeen: Maybe; - }; -} - export namespace GetHostFirstLastSeenQuery { export type Variables = { sourceId: string; @@ -2935,11 +3040,11 @@ export namespace GetHostOverviewQuery { }; } -export namespace GetIpOverviewQuery { +export namespace GetKpiHostDetailsQuery { export type Variables = { sourceId: string; + timerange: TimerangeInput; filterQuery?: Maybe; - ip: string; defaultIndex: string[]; inspect: boolean; }; @@ -2955,154 +3060,106 @@ export namespace GetIpOverviewQuery { id: string; - IpOverview: Maybe; + KpiHostDetails: KpiHostDetails; }; - export type IpOverview = { - __typename?: 'IpOverviewData'; + export type KpiHostDetails = { + __typename?: 'KpiHostDetailsData'; - source: Maybe<_Source>; + authSuccess: Maybe; - destination: Maybe; + authSuccessHistogram: Maybe; - host: Host; + authFailure: Maybe; - inspect: Maybe; - }; + authFailureHistogram: Maybe; - export type _Source = { - __typename?: 'Overview'; + uniqueSourceIps: Maybe; - firstSeen: Maybe; + uniqueSourceIpsHistogram: Maybe; - lastSeen: Maybe; + uniqueDestinationIps: Maybe; - autonomousSystem: AutonomousSystem; + uniqueDestinationIpsHistogram: Maybe; - geo: Geo; + inspect: Maybe; }; - export type AutonomousSystem = { - __typename?: 'AutonomousSystem'; + export type AuthSuccessHistogram = KpiHostDetailsChartFields.Fragment; - number: Maybe; + export type AuthFailureHistogram = KpiHostDetailsChartFields.Fragment; - organization: Maybe; - }; + export type UniqueSourceIpsHistogram = KpiHostDetailsChartFields.Fragment; - export type Organization = { - __typename?: 'AutonomousSystemOrganization'; + export type UniqueDestinationIpsHistogram = KpiHostDetailsChartFields.Fragment; - name: Maybe; - }; + export type Inspect = { + __typename?: 'Inspect'; - export type Geo = { - __typename?: 'GeoEcsFields'; + dsl: string[]; - continent_name: Maybe; + response: string[]; + }; +} - city_name: Maybe; +export namespace GetKpiHostsQuery { + export type Variables = { + sourceId: string; + timerange: TimerangeInput; + filterQuery?: Maybe; + defaultIndex: string[]; + inspect: boolean; + }; - country_iso_code: Maybe; + export type Query = { + __typename?: 'Query'; - country_name: Maybe; + source: Source; + }; - location: Maybe; + export type Source = { + __typename?: 'Source'; - region_iso_code: Maybe; + id: string; - region_name: Maybe; + KpiHosts: KpiHosts; }; - export type Location = { - __typename?: 'Location'; + export type KpiHosts = { + __typename?: 'KpiHostsData'; - lat: Maybe; + hosts: Maybe; - lon: Maybe; - }; + hostsHistogram: Maybe; - export type Destination = { - __typename?: 'Overview'; + authSuccess: Maybe; - firstSeen: Maybe; + authSuccessHistogram: Maybe; - lastSeen: Maybe; + authFailure: Maybe; - autonomousSystem: _AutonomousSystem; + authFailureHistogram: Maybe; - geo: _Geo; - }; - - export type _AutonomousSystem = { - __typename?: 'AutonomousSystem'; - - number: Maybe; - - organization: Maybe<_Organization>; - }; - - export type _Organization = { - __typename?: 'AutonomousSystemOrganization'; - - name: Maybe; - }; - - export type _Geo = { - __typename?: 'GeoEcsFields'; - - continent_name: Maybe; - - city_name: Maybe; - - country_iso_code: Maybe; - - country_name: Maybe; - - location: Maybe<_Location>; - - region_iso_code: Maybe; - - region_name: Maybe; - }; - - export type _Location = { - __typename?: 'Location'; - - lat: Maybe; - - lon: Maybe; - }; - - export type Host = { - __typename?: 'HostEcsFields'; - - architecture: Maybe; - - id: Maybe; - - ip: Maybe; + uniqueSourceIps: Maybe; - mac: Maybe; + uniqueSourceIpsHistogram: Maybe; - name: Maybe; + uniqueDestinationIps: Maybe; - os: Maybe; + uniqueDestinationIpsHistogram: Maybe; - type: Maybe; + inspect: Maybe; }; - export type Os = { - __typename?: 'OsEcsFields'; + export type HostsHistogram = KpiHostChartFields.Fragment; - family: Maybe; + export type AuthSuccessHistogram = KpiHostChartFields.Fragment; - name: Maybe; + export type AuthFailureHistogram = KpiHostChartFields.Fragment; - platform: Maybe; + export type UniqueSourceIpsHistogram = KpiHostChartFields.Fragment; - version: Maybe; - }; + export type UniqueDestinationIpsHistogram = KpiHostChartFields.Fragment; export type Inspect = { __typename?: 'Inspect'; @@ -3113,10 +3170,11 @@ export namespace GetIpOverviewQuery { }; } -export namespace GetKpiHostDetailsQuery { +export namespace GetUncommonProcessesQuery { export type Variables = { sourceId: string; timerange: TimerangeInput; + pagination: PaginationInputPaginated; filterQuery?: Maybe; defaultIndex: string[]; inspect: boolean; @@ -3133,106 +3191,80 @@ export namespace GetKpiHostDetailsQuery { id: string; - KpiHostDetails: KpiHostDetails; + UncommonProcesses: UncommonProcesses; }; - export type KpiHostDetails = { - __typename?: 'KpiHostDetailsData'; - - authSuccess: Maybe; - - authSuccessHistogram: Maybe; - - authFailure: Maybe; - - authFailureHistogram: Maybe; - - uniqueSourceIps: Maybe; + export type UncommonProcesses = { + __typename?: 'UncommonProcessesData'; - uniqueSourceIpsHistogram: Maybe; + totalCount: number; - uniqueDestinationIps: Maybe; + edges: Edges[]; - uniqueDestinationIpsHistogram: Maybe; + pageInfo: PageInfo; inspect: Maybe; }; - export type AuthSuccessHistogram = KpiHostDetailsChartFields.Fragment; - - export type AuthFailureHistogram = KpiHostDetailsChartFields.Fragment; - - export type UniqueSourceIpsHistogram = KpiHostDetailsChartFields.Fragment; - - export type UniqueDestinationIpsHistogram = KpiHostDetailsChartFields.Fragment; - - export type Inspect = { - __typename?: 'Inspect'; + export type Edges = { + __typename?: 'UncommonProcessesEdges'; - dsl: string[]; + node: Node; - response: string[]; + cursor: Cursor; }; -} -export namespace GetKpiHostsQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; + export type Node = { + __typename?: 'UncommonProcessItem'; - export type Query = { - __typename?: 'Query'; + _id: string; - source: Source; - }; + instances: number; - export type Source = { - __typename?: 'Source'; + process: Process; - id: string; + user: Maybe; - KpiHosts: KpiHosts; + hosts: Hosts[]; }; - export type KpiHosts = { - __typename?: 'KpiHostsData'; - - hosts: Maybe; - - hostsHistogram: Maybe; + export type Process = { + __typename?: 'ProcessEcsFields'; - authSuccess: Maybe; + args: Maybe; - authSuccessHistogram: Maybe; + name: Maybe; + }; - authFailure: Maybe; + export type User = { + __typename?: 'UserEcsFields'; - authFailureHistogram: Maybe; + id: Maybe; - uniqueSourceIps: Maybe; + name: Maybe; + }; - uniqueSourceIpsHistogram: Maybe; + export type Hosts = { + __typename?: 'HostEcsFields'; - uniqueDestinationIps: Maybe; + name: Maybe; + }; - uniqueDestinationIpsHistogram: Maybe; + export type Cursor = { + __typename?: 'CursorType'; - inspect: Maybe; + value: Maybe; }; - export type HostsHistogram = KpiHostChartFields.Fragment; - - export type AuthSuccessHistogram = KpiHostChartFields.Fragment; + export type PageInfo = { + __typename?: 'PageInfoPaginated'; - export type AuthFailureHistogram = KpiHostChartFields.Fragment; + activePage: number; - export type UniqueSourceIpsHistogram = KpiHostChartFields.Fragment; + fakeTotalCount: number; - export type UniqueDestinationIpsHistogram = KpiHostChartFields.Fragment; + showMorePagesIndicator: boolean; + }; export type Inspect = { __typename?: 'Inspect'; @@ -3243,11 +3275,11 @@ export namespace GetKpiHostsQuery { }; } -export namespace GetKpiNetworkQuery { +export namespace GetIpOverviewQuery { export type Variables = { sourceId: string; - timerange: TimerangeInput; filterQuery?: Maybe; + ip: string; defaultIndex: string[]; inspect: boolean; }; @@ -3263,89 +3295,213 @@ export namespace GetKpiNetworkQuery { id: string; - KpiNetwork: Maybe; + IpOverview: Maybe; }; - export type KpiNetwork = { - __typename?: 'KpiNetworkData'; - - networkEvents: Maybe; - - uniqueFlowId: Maybe; - - uniqueSourcePrivateIps: Maybe; - - uniqueSourcePrivateIpsHistogram: Maybe; - - uniqueDestinationPrivateIps: Maybe; + export type IpOverview = { + __typename?: 'IpOverviewData'; - uniqueDestinationPrivateIpsHistogram: Maybe; + source: Maybe<_Source>; - dnsQueries: Maybe; + destination: Maybe; - tlsHandshakes: Maybe; + host: Host; inspect: Maybe; }; - export type UniqueSourcePrivateIpsHistogram = KpiNetworkChartFields.Fragment; + export type _Source = { + __typename?: 'Overview'; - export type UniqueDestinationPrivateIpsHistogram = KpiNetworkChartFields.Fragment; + firstSeen: Maybe; - export type Inspect = { - __typename?: 'Inspect'; + lastSeen: Maybe; - dsl: string[]; + autonomousSystem: AutonomousSystem; - response: string[]; + geo: Geo; }; -} -export namespace GetMatrixHistogramQuery { - export type Variables = { - defaultIndex: string[]; - filterQuery?: Maybe; - histogramType: HistogramType; - inspect: boolean; - sourceId: string; - stackByField: string; - timerange: TimerangeInput; - }; + export type AutonomousSystem = { + __typename?: 'AutonomousSystem'; - export type Query = { - __typename?: 'Query'; + number: Maybe; - source: Source; + organization: Maybe; }; - export type Source = { - __typename?: 'Source'; - - id: string; + export type Organization = { + __typename?: 'AutonomousSystemOrganization'; - MatrixHistogram: MatrixHistogram; + name: Maybe; }; - export type MatrixHistogram = { - __typename?: 'MatrixHistogramOverTimeData'; + export type Geo = { + __typename?: 'GeoEcsFields'; - matrixHistogramData: MatrixHistogramData[]; + continent_name: Maybe; - totalCount: number; + city_name: Maybe; - inspect: Maybe; - }; + country_iso_code: Maybe; - export type MatrixHistogramData = { - __typename?: 'MatrixOverTimeHistogramData'; + country_name: Maybe; - x: Maybe; + location: Maybe; - y: Maybe; + region_iso_code: Maybe; - g: Maybe; + region_name: Maybe; + }; + + export type Location = { + __typename?: 'Location'; + + lat: Maybe; + + lon: Maybe; + }; + + export type Destination = { + __typename?: 'Overview'; + + firstSeen: Maybe; + + lastSeen: Maybe; + + autonomousSystem: _AutonomousSystem; + + geo: _Geo; + }; + + export type _AutonomousSystem = { + __typename?: 'AutonomousSystem'; + + number: Maybe; + + organization: Maybe<_Organization>; + }; + + export type _Organization = { + __typename?: 'AutonomousSystemOrganization'; + + name: Maybe; + }; + + export type _Geo = { + __typename?: 'GeoEcsFields'; + + continent_name: Maybe; + + city_name: Maybe; + + country_iso_code: Maybe; + + country_name: Maybe; + + location: Maybe<_Location>; + + region_iso_code: Maybe; + + region_name: Maybe; + }; + + export type _Location = { + __typename?: 'Location'; + + lat: Maybe; + + lon: Maybe; + }; + + export type Host = { + __typename?: 'HostEcsFields'; + + architecture: Maybe; + + id: Maybe; + + ip: Maybe; + + mac: Maybe; + + name: Maybe; + + os: Maybe; + + type: Maybe; + }; + + export type Os = { + __typename?: 'OsEcsFields'; + + family: Maybe; + + name: Maybe; + + platform: Maybe; + + version: Maybe; + }; + + export type Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; + }; +} + +export namespace GetKpiNetworkQuery { + export type Variables = { + sourceId: string; + timerange: TimerangeInput; + filterQuery?: Maybe; + defaultIndex: string[]; + inspect: boolean; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + KpiNetwork: Maybe; + }; + + export type KpiNetwork = { + __typename?: 'KpiNetworkData'; + + networkEvents: Maybe; + + uniqueFlowId: Maybe; + + uniqueSourcePrivateIps: Maybe; + + uniqueSourcePrivateIpsHistogram: Maybe; + + uniqueDestinationPrivateIps: Maybe; + + uniqueDestinationPrivateIpsHistogram: Maybe; + + dnsQueries: Maybe; + + tlsHandshakes: Maybe; + + inspect: Maybe; }; + export type UniqueSourcePrivateIpsHistogram = KpiNetworkChartFields.Fragment; + + export type UniqueDestinationPrivateIpsHistogram = KpiNetworkChartFields.Fragment; + export type Inspect = { __typename?: 'Inspect'; @@ -3832,11 +3988,15 @@ export namespace GetNetworkTopNFlowQuery { }; } -export namespace GetOverviewHostQuery { +export namespace GetTlsQuery { export type Variables = { sourceId: string; - timerange: TimerangeInput; filterQuery?: Maybe; + flowTarget: FlowTargetSourceDest; + ip: string; + pagination: PaginationInputPaginated; + sort: TlsSortField; + timerange: TimerangeInput; defaultIndex: string[]; inspect: boolean; }; @@ -3852,45 +4012,57 @@ export namespace GetOverviewHostQuery { id: string; - OverviewHost: Maybe; + Tls: Tls; }; - export type OverviewHost = { - __typename?: 'OverviewHostData'; + export type Tls = { + __typename?: 'TlsData'; - auditbeatAuditd: Maybe; + totalCount: number; - auditbeatFIM: Maybe; + edges: Edges[]; - auditbeatLogin: Maybe; + pageInfo: PageInfo; - auditbeatPackage: Maybe; + inspect: Maybe; + }; - auditbeatProcess: Maybe; + export type Edges = { + __typename?: 'TlsEdges'; - auditbeatUser: Maybe; + node: Node; - endgameDns: Maybe; + cursor: Cursor; + }; - endgameFile: Maybe; + export type Node = { + __typename?: 'TlsNode'; - endgameImageLoad: Maybe; + _id: Maybe; - endgameNetwork: Maybe; + subjects: Maybe; - endgameProcess: Maybe; + ja3: Maybe; - endgameRegistry: Maybe; + issuers: Maybe; - endgameSecurity: Maybe; + notAfter: Maybe; + }; - filebeatSystemModule: Maybe; + export type Cursor = { + __typename?: 'CursorType'; - winlogbeatSecurity: Maybe; + value: Maybe; + }; - winlogbeatMWSysmonOperational: Maybe; + export type PageInfo = { + __typename?: 'PageInfoPaginated'; - inspect: Maybe; + activePage: number; + + fakeTotalCount: number; + + showMorePagesIndicator: boolean; }; export type Inspect = { @@ -3902,11 +4074,15 @@ export namespace GetOverviewHostQuery { }; } -export namespace GetOverviewNetworkQuery { +export namespace GetUsersQuery { export type Variables = { sourceId: string; - timerange: TimerangeInput; filterQuery?: Maybe; + flowTarget: FlowTarget; + ip: string; + pagination: PaginationInputPaginated; + sort: UsersSortField; + timerange: TimerangeInput; defaultIndex: string[]; inspect: boolean; }; @@ -3922,35 +4098,67 @@ export namespace GetOverviewNetworkQuery { id: string; - OverviewNetwork: Maybe; + Users: Users; }; - export type OverviewNetwork = { - __typename?: 'OverviewNetworkData'; - - auditbeatSocket: Maybe; + export type Users = { + __typename?: 'UsersData'; - filebeatCisco: Maybe; + totalCount: number; - filebeatNetflow: Maybe; + edges: Edges[]; - filebeatPanw: Maybe; + pageInfo: PageInfo; - filebeatSuricata: Maybe; + inspect: Maybe; + }; - filebeatZeek: Maybe; + export type Edges = { + __typename?: 'UsersEdges'; - packetbeatDNS: Maybe; + node: Node; - packetbeatFlow: Maybe; + cursor: Cursor; + }; - packetbeatTLS: Maybe; + export type Node = { + __typename?: 'UsersNode'; - inspect: Maybe; + user: Maybe; }; - export type Inspect = { - __typename?: 'Inspect'; + export type User = { + __typename?: 'UsersItem'; + + name: Maybe; + + id: Maybe; + + groupId: Maybe; + + groupName: Maybe; + + count: Maybe; + }; + + export type Cursor = { + __typename?: 'CursorType'; + + value: Maybe; + }; + + export type PageInfo = { + __typename?: 'PageInfoPaginated'; + + activePage: number; + + fakeTotalCount: number; + + showMorePagesIndicator: boolean; + }; + + export type Inspect = { + __typename?: 'Inspect'; dsl: string[]; @@ -3958,10 +4166,13 @@ export namespace GetOverviewNetworkQuery { }; } -export namespace SourceQuery { +export namespace GetOverviewHostQuery { export type Variables = { - sourceId?: Maybe; + sourceId: string; + timerange: TimerangeInput; + filterQuery?: Maybe; defaultIndex: string[]; + inspect: boolean; }; export type Query = { @@ -3975,37 +4186,109 @@ export namespace SourceQuery { id: string; - status: Status; + OverviewHost: Maybe; }; - export type Status = { - __typename?: 'SourceStatus'; + export type OverviewHost = { + __typename?: 'OverviewHostData'; - indicesExist: boolean; + auditbeatAuditd: Maybe; - indexFields: IndexFields[]; + auditbeatFIM: Maybe; + + auditbeatLogin: Maybe; + + auditbeatPackage: Maybe; + + auditbeatProcess: Maybe; + + auditbeatUser: Maybe; + + endgameDns: Maybe; + + endgameFile: Maybe; + + endgameImageLoad: Maybe; + + endgameNetwork: Maybe; + + endgameProcess: Maybe; + + endgameRegistry: Maybe; + + endgameSecurity: Maybe; + + filebeatSystemModule: Maybe; + + winlogbeatSecurity: Maybe; + + winlogbeatMWSysmonOperational: Maybe; + + inspect: Maybe; }; - export type IndexFields = { - __typename?: 'IndexField'; + export type Inspect = { + __typename?: 'Inspect'; - category: string; + dsl: string[]; - description: Maybe; + response: string[]; + }; +} - example: Maybe; +export namespace GetOverviewNetworkQuery { + export type Variables = { + sourceId: string; + timerange: TimerangeInput; + filterQuery?: Maybe; + defaultIndex: string[]; + inspect: boolean; + }; - indexes: (Maybe)[]; + export type Query = { + __typename?: 'Query'; - name: string; + source: Source; + }; - searchable: boolean; + export type Source = { + __typename?: 'Source'; - type: string; + id: string; - aggregatable: boolean; + OverviewNetwork: Maybe; + }; - format: Maybe; + export type OverviewNetwork = { + __typename?: 'OverviewNetworkData'; + + auditbeatSocket: Maybe; + + filebeatCisco: Maybe; + + filebeatNetflow: Maybe; + + filebeatPanw: Maybe; + + filebeatSuricata: Maybe; + + filebeatZeek: Maybe; + + packetbeatDNS: Maybe; + + packetbeatFlow: Maybe; + + packetbeatTLS: Maybe; + + inspect: Maybe; + }; + + export type Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; }; } @@ -5662,289 +5945,6 @@ export namespace PersistTimelinePinnedEventMutation { }; } -export namespace GetTlsQuery { - export type Variables = { - sourceId: string; - filterQuery?: Maybe; - flowTarget: FlowTargetSourceDest; - ip: string; - pagination: PaginationInputPaginated; - sort: TlsSortField; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - Tls: Tls; - }; - - export type Tls = { - __typename?: 'TlsData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'TlsEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'TlsNode'; - - _id: Maybe; - - subjects: Maybe; - - ja3: Maybe; - - issuers: Maybe; - - notAfter: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetUncommonProcessesQuery { - export type Variables = { - sourceId: string; - timerange: TimerangeInput; - pagination: PaginationInputPaginated; - filterQuery?: Maybe; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - UncommonProcesses: UncommonProcesses; - }; - - export type UncommonProcesses = { - __typename?: 'UncommonProcessesData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'UncommonProcessesEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'UncommonProcessItem'; - - _id: string; - - instances: number; - - process: Process; - - user: Maybe; - - hosts: Hosts[]; - }; - - export type Process = { - __typename?: 'ProcessEcsFields'; - - args: Maybe; - - name: Maybe; - }; - - export type User = { - __typename?: 'UserEcsFields'; - - id: Maybe; - - name: Maybe; - }; - - export type Hosts = { - __typename?: 'HostEcsFields'; - - name: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - -export namespace GetUsersQuery { - export type Variables = { - sourceId: string; - filterQuery?: Maybe; - flowTarget: FlowTarget; - ip: string; - pagination: PaginationInputPaginated; - sort: UsersSortField; - timerange: TimerangeInput; - defaultIndex: string[]; - inspect: boolean; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'Source'; - - id: string; - - Users: Users; - }; - - export type Users = { - __typename?: 'UsersData'; - - totalCount: number; - - edges: Edges[]; - - pageInfo: PageInfo; - - inspect: Maybe; - }; - - export type Edges = { - __typename?: 'UsersEdges'; - - node: Node; - - cursor: Cursor; - }; - - export type Node = { - __typename?: 'UsersNode'; - - user: Maybe; - }; - - export type User = { - __typename?: 'UsersItem'; - - name: Maybe; - - id: Maybe; - - groupId: Maybe; - - groupName: Maybe; - - count: Maybe; - }; - - export type Cursor = { - __typename?: 'CursorType'; - - value: Maybe; - }; - - export type PageInfo = { - __typename?: 'PageInfoPaginated'; - - activePage: number; - - fakeTotalCount: number; - - showMorePagesIndicator: boolean; - }; - - export type Inspect = { - __typename?: 'Inspect'; - - dsl: string[]; - - response: string[]; - }; -} - export namespace KpiHostDetailsChartFields { export type Fragment = { __typename?: 'KpiHostHistogramData'; diff --git a/x-pack/plugins/siem/scripts/generate_types_from_graphql.js b/x-pack/plugins/siem/scripts/generate_types_from_graphql.js index e6b063dfd2c07..21a37e31c6e80 100644 --- a/x-pack/plugins/siem/scripts/generate_types_from_graphql.js +++ b/x-pack/plugins/siem/scripts/generate_types_from_graphql.js @@ -11,7 +11,7 @@ const { join, resolve } = require('path'); const { generate } = require('graphql-code-generator'); const GRAPHQL_GLOBS = [ - join('public', 'containers', '**', '*.gql_query.ts{,x}'), + join('public', '**', '*.gql_query.ts{,x}'), join('common', 'graphql', '**', '*.gql_query.ts{,x}'), ]; const OUTPUT_INTROSPECTION_PATH = resolve('public', 'graphql', 'introspection.json'); diff --git a/x-pack/plugins/siem/server/index.ts b/x-pack/plugins/siem/server/index.ts index e9cd78589fac9..586b9dec2f4ab 100644 --- a/x-pack/plugins/siem/server/index.ts +++ b/x-pack/plugins/siem/server/index.ts @@ -15,3 +15,17 @@ export const plugin = (context: PluginInitializerContext) => { export const config = { schema: configSchema }; export { ConfigType, Plugin, PluginSetup, PluginStart }; + +// Exports to be shared with plugins such as x-pack/lists plugin +export { deleteTemplate } from './lib/detection_engine/index/delete_template'; +export { deletePolicy } from './lib/detection_engine/index/delete_policy'; +export { deleteAllIndex } from './lib/detection_engine/index/delete_all_index'; +export { setPolicy } from './lib/detection_engine/index/set_policy'; +export { setTemplate } from './lib/detection_engine/index/set_template'; +export { getTemplateExists } from './lib/detection_engine/index/get_template_exists'; +export { getPolicyExists } from './lib/detection_engine/index/get_policy_exists'; +export { createBootstrapIndex } from './lib/detection_engine/index/create_bootstrap_index'; +export { getIndexExists } from './lib/detection_engine/index/get_index_exists'; +export { buildRouteValidation } from './utils/build_validation/route_validation'; +export { validate } from './lib/detection_engine/routes/rules/validate'; +export { transformError, buildSiemResponse } from './lib/detection_engine/routes/utils'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts index cfba40fc225a2..cda3a4b81ed9b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/validate.ts @@ -9,8 +9,8 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import * as t from 'io-ts'; -import { formatErrors } from '../../../../utils/build_validation/format_errors'; -import { exactCheck } from '../../../../utils/build_validation/exact_check'; +import { formatErrors } from '../../../../../common/format_errors'; +import { exactCheck } from '../../../../../common/exact_check'; import { PartialAlert, FindResult } from '../../../../../../alerting/server'; import { isAlertType, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts index fbd2382e2826d..0b0d3bf43b1e9 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/check_type_dependents.test.ts @@ -20,8 +20,8 @@ import { left } from 'fp-ts/lib/Either'; import { RulesSchema } from './rules_schema'; import { TypeAndTimelineOnly } from './type_timeline_only_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('check_type_dependents', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts index 6e159a792edb6..9bbde3d5236db 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/error_schema.test.ts @@ -10,8 +10,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { getErrorPayload } from './__mocks__/utils'; import { errorSchema, ErrorSchema } from './error_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('error_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts index 68b67db595d76..1b7d7994462c7 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/find_rules_schema.test.ts @@ -10,8 +10,8 @@ import { getFindResponseSingle, getBaseResponsePayload } from './__mocks__/utils import { left } from 'fp-ts/lib/Either'; import { RulesSchema } from './rules_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { getPaths, foldLeftRight } from '../../../../../utils/build_validation/__mocks__/utils'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../../../../common/exact_check'; describe('find_rules_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts index b0b863ebbbc0b..18e17a319883a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/import_rules_schema.test.ts @@ -9,9 +9,9 @@ import { left, Either } from 'fp-ts/lib/Either'; import { ImportRulesSchema, importRulesSchema } from './import_rules_schema'; import { ErrorSchema } from './error_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { getPaths, foldLeftRight } from '../../../../../utils/build_validation/__mocks__/utils'; import { Errors } from 'io-ts'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('import_rules_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts index 827167c63fd58..2d3fd75914822 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_schema.test.ts @@ -8,8 +8,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; import { PrePackagedRulesSchema, prePackagedRulesSchema } from './prepackaged_rules_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('prepackaged_rules_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts index a864667583c0a..abe601a546111 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/prepackaged_rules_status_schema.test.ts @@ -11,8 +11,8 @@ import { prePackagedRulesStatusSchema, } from './prepackaged_rules_status_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('prepackaged_rules_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts index 9a7cf5e2c2871..98cb2ef058485 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_bulk_schema.test.ts @@ -12,8 +12,8 @@ import { RulesBulkSchema, rulesBulkSchema } from './rules_bulk_schema'; import { RulesSchema } from './rules_schema'; import { ErrorSchema } from './error_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('prepackaged_rule_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts index 82a6682e6461f..ade4d12517aca 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/rules_schema.test.ts @@ -10,8 +10,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { rulesSchema, RulesSchema, removeList } from './rules_schema'; import { getBaseResponsePayload } from './__mocks__/utils'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; +import { exactCheck } from '../../../../../../common/exact_check'; export const ANCHOR_DATE = '2020-02-20T03:57:54.037Z'; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts index 85fb124464487..8f06e2c6e49b0 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/response/type_timeline_only_schema.test.ts @@ -9,8 +9,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { TypeAndTimelineOnly, typeAndTimelineOnlySchema } from './type_timeline_only_schema'; import { setFeatureFlagsForTestsOnly, unSetFeatureFlagsForTestsOnly } from '../../../feature_flags'; -import { exactCheck } from '../../../../../utils/build_validation/exact_check'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { exactCheck } from '../../../../../../common/exact_check'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('prepackaged_rule_schema', () => { beforeAll(() => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts index ff62ea4443f3f..9f9181359d44a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/iso_date_string.test.ts @@ -7,7 +7,7 @@ import { IsoDateString } from './iso_date_string'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('ios_date_string', () => { test('it should validate a iso string', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts index 2a97c8a4a143e..dc0bd6cacf0d6 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts @@ -7,7 +7,7 @@ import { ListsDefaultArray } from './lists_default_array'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('lists_default_array', () => { test('it should validate an empty array', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts index d6f21681df88f..a3338c878bd71 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/positive_integer_greater_than_zero.test.ts @@ -7,7 +7,7 @@ import { PositiveIntegerGreaterThanZero } from './positive_integer_greater_than_zero'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('positive_integer_greater_than_zero', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts index 26441745a7f29..48ea2025b9b12 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/postive_integer.test.ts @@ -7,7 +7,7 @@ import { PositiveInteger } from './positive_integer'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('positive_integer_greater_than_zero', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts index 76f722274ce23..3aaff7e00ad51 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/references_default_array.test.ts @@ -7,7 +7,7 @@ import { ReferencesDefaultArray } from './references_default_array'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { getPaths, foldLeftRight } from '../../../../../../common/test_utils'; describe('references_default_array', () => { test('it should validate an empty array', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts index 76e3445358dd9..41c0faf4d608d 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/risk_score.test.ts @@ -7,7 +7,7 @@ import { RiskScore } from './risk_score'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('risk_score', () => { test('it should validate a positive number', () => { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts index 7b68dbcef2d7e..b640b449e6b8a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/schemas/types/uuid.test.ts @@ -7,7 +7,7 @@ import { UUID } from './uuid'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; -import { foldLeftRight, getPaths } from '../../../../../utils/build_validation/__mocks__/utils'; +import { foldLeftRight, getPaths } from '../../../../../../common/test_utils'; describe('uuid', () => { test('it should validate a uuid', () => { diff --git a/x-pack/plugins/siem/server/utils/build_validation/route_validation.ts b/x-pack/plugins/siem/server/utils/build_validation/route_validation.ts index 30b95dcfa94ee..19353080c15cb 100644 --- a/x-pack/plugins/siem/server/utils/build_validation/route_validation.ts +++ b/x-pack/plugins/siem/server/utils/build_validation/route_validation.ts @@ -7,13 +7,13 @@ import { fold } from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; import * as rt from 'io-ts'; +import { formatErrors } from '../../../common/format_errors'; +import { exactCheck } from '../../../common/exact_check'; import { RouteValidationFunction, RouteValidationResultFactory, RouteValidationError, } from '../../../../../../src/core/server'; -import { exactCheck } from './exact_check'; -import { formatErrors } from './format_errors'; type RequestValidationResult = | { diff --git a/x-pack/plugins/snapshot_restore/public/application/constants/index.ts b/x-pack/plugins/snapshot_restore/public/application/constants/index.ts index ea6f5c80b9343..ad95c19870d15 100644 --- a/x-pack/plugins/snapshot_restore/public/application/constants/index.ts +++ b/x-pack/plugins/snapshot_restore/public/application/constants/index.ts @@ -6,7 +6,7 @@ import { DAY } from '../../shared_imports'; -export const BASE_PATH = '/management/elasticsearch/snapshot_restore'; +export const BASE_PATH = '/management/data/snapshot_restore'; export const DEFAULT_SECTION: Section = 'snapshots'; export type Section = 'repositories' | 'snapshots' | 'restore_status' | 'policies'; diff --git a/x-pack/plugins/snapshot_restore/public/plugin.ts b/x-pack/plugins/snapshot_restore/public/plugin.ts index d966d0c32651c..13a15c5711ba9 100644 --- a/x-pack/plugins/snapshot_restore/public/plugin.ts +++ b/x-pack/plugins/snapshot_restore/public/plugin.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, PluginInitializerContext } from 'src/core/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { PLUGIN } from '../common/constants'; import { ClientConfigType } from './types'; @@ -40,12 +40,12 @@ export class SnapshotRestoreUIPlugin { textService.setup(i18n); httpService.setup(http); - management.sections.getSection('elasticsearch')!.registerApp({ + management.sections.getSection(ManagementSectionId.Data).registerApp({ id: PLUGIN.id, title: i18n.translate('xpack.snapshotRestore.appTitle', { defaultMessage: 'Snapshot and Restore', }), - order: 7, + order: 3, mount: async params => { const { mountManagementSection } = await import('./application/mount_management_section'); const services = { diff --git a/x-pack/plugins/snapshot_restore/server/plugin.ts b/x-pack/plugins/snapshot_restore/server/plugin.ts index 00ff3db976d66..c5d3c665a3b7f 100644 --- a/x-pack/plugins/snapshot_restore/server/plugin.ts +++ b/x-pack/plugins/snapshot_restore/server/plugin.ts @@ -13,6 +13,7 @@ import { first } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import { CoreSetup, + ICustomClusterClient, Plugin, Logger, PluginInitializerContext, @@ -31,10 +32,17 @@ export interface SnapshotRestoreContext { client: IScopedClusterClient; } +async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { + const [core] = await getStartServices(); + const esClientConfig = { plugins: [elasticsearchJsPlugin] }; + return core.elasticsearch.legacy.createClient('snapshotRestore', esClientConfig); +} + export class SnapshotRestoreServerPlugin implements Plugin { private readonly logger: Logger; private readonly apiRoutes: ApiRoutes; private readonly license: License; + private snapshotRestoreESClient?: ICustomClusterClient; constructor(private context: PluginInitializerContext) { const { logger } = this.context; @@ -44,7 +52,7 @@ export class SnapshotRestoreServerPlugin implements Plugin } public async setup( - { http, elasticsearch }: CoreSetup, + { http, getStartServices }: CoreSetup, { licensing, security, cloud }: Dependencies ): Promise { const pluginConfig = await this.context.config @@ -72,11 +80,11 @@ export class SnapshotRestoreServerPlugin implements Plugin } ); - const esClientConfig = { plugins: [elasticsearchJsPlugin] }; - const snapshotRestoreESClient = elasticsearch.createClient('snapshotRestore', esClientConfig); - http.registerRouteHandlerContext('snapshotRestore', (ctx, request) => { + http.registerRouteHandlerContext('snapshotRestore', async (ctx, request) => { + this.snapshotRestoreESClient = + this.snapshotRestoreESClient ?? (await getCustomEsClient(getStartServices)); return { - client: snapshotRestoreESClient.asScoped(request), + client: this.snapshotRestoreESClient.asScoped(request), }; }); @@ -95,11 +103,11 @@ export class SnapshotRestoreServerPlugin implements Plugin }); } - public start() { - this.logger.debug('Starting plugin'); - } + public start() {} public stop() { - this.logger.debug('Stopping plugin'); + if (this.snapshotRestoreESClient) { + this.snapshotRestoreESClient.close(); + } } } diff --git a/x-pack/plugins/spaces/public/management/management_service.test.ts b/x-pack/plugins/spaces/public/management/management_service.test.ts index 782c261be9664..7f8acccf71d88 100644 --- a/x-pack/plugins/spaces/public/management/management_service.test.ts +++ b/x-pack/plugins/spaces/public/management/management_service.test.ts @@ -39,7 +39,7 @@ describe('ManagementService', () => { expect(mockKibanaSection.registerApp).toHaveBeenCalledWith({ id: 'spaces', title: 'Spaces', - order: 10, + order: 2, mount: expect.any(Function), }); }); diff --git a/x-pack/plugins/spaces/public/management/management_service.tsx b/x-pack/plugins/spaces/public/management/management_service.tsx index cec4bee1373ca..297df07636849 100644 --- a/x-pack/plugins/spaces/public/management/management_service.tsx +++ b/x-pack/plugins/spaces/public/management/management_service.tsx @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ManagementSetup, ManagementApp } from 'src/plugins/management/public'; import { StartServicesAccessor, Capabilities } from 'src/core/public'; +import { + ManagementSetup, + ManagementApp, + ManagementSectionId, +} from '../../../../../src/plugins/management/public'; import { SecurityLicense } from '../../../security/public'; import { SpacesManager } from '../spaces_manager'; import { PluginsStart } from '../plugin'; @@ -25,12 +29,11 @@ export class ManagementService { private registeredSpacesManagementApp?: ManagementApp; public setup({ getStartServices, management, spacesManager, securityLicense }: SetupDeps) { - const kibanaSection = management.sections.getSection('kibana'); - if (kibanaSection) { - this.registeredSpacesManagementApp = kibanaSection.registerApp( + this.registeredSpacesManagementApp = management.sections + .getSection(ManagementSectionId.Kibana) + .registerApp( spacesManagementApp.create({ getStartServices, spacesManager, securityLicense }) ); - } } public start({ capabilities }: StartDeps) { diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 61c872ec9269c..92c78d63d1b2e 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -70,7 +70,7 @@ describe('spacesManagementApp', () => { Object { "id": "spaces", "mount": [Function], - "order": 10, + "order": 2, "title": "Spaces", } `); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx index 92b369807b0da..079cf2234b13b 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.tsx @@ -28,7 +28,7 @@ export const spacesManagementApp = Object.freeze({ create({ getStartServices, spacesManager, securityLicense }: CreateParams) { return { id: this.id, - order: 10, + order: 2, title: i18n.translate('xpack.spaces.displayName', { defaultMessage: 'Spaces', }), diff --git a/x-pack/plugins/spaces/public/plugin.test.ts b/x-pack/plugins/spaces/public/plugin.test.ts index d879a318fe815..a98f593f546a0 100644 --- a/x-pack/plugins/spaces/public/plugin.test.ts +++ b/x-pack/plugins/spaces/public/plugin.test.ts @@ -7,7 +7,7 @@ import { coreMock } from 'src/core/public/mocks'; import { SpacesPlugin } from './plugin'; import { homePluginMock } from '../../../../src/plugins/home/public/mocks'; -import { ManagementSection } from '../../../../src/plugins/management/public'; +import { ManagementSection, ManagementSectionId } from '../../../../src/plugins/management/public'; import { managementPluginMock } from '../../../../src/plugins/management/public/mocks'; import { advancedSettingsMock } from '../../../../src/plugins/advanced_settings/public/mocks'; import { featuresPluginMock } from '../../features/public/mocks'; @@ -35,7 +35,7 @@ describe('Spaces plugin', () => { const kibanaSection = new ManagementSection( { - id: 'kibana', + id: ManagementSectionId.Kibana, title: 'Mock Kibana Section', order: 1, }, diff --git a/x-pack/plugins/transform/public/app/constants/index.ts b/x-pack/plugins/transform/public/app/constants/index.ts index 5d71980c83714..3156fae7545b1 100644 --- a/x-pack/plugins/transform/public/app/constants/index.ts +++ b/x-pack/plugins/transform/public/app/constants/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CLIENT_BASE_PATH = '/management/elasticsearch/transform/'; +export const CLIENT_BASE_PATH = '/management/data/transform/'; export enum SECTION_SLUG { HOME = 'transform_management', diff --git a/x-pack/plugins/transform/public/plugin.ts b/x-pack/plugins/transform/public/plugin.ts index 563a569fe95b5..1411f80cecd2e 100644 --- a/x-pack/plugins/transform/public/plugin.ts +++ b/x-pack/plugins/transform/public/plugin.ts @@ -7,8 +7,8 @@ import { i18n as kbnI18n } from '@kbn/i18n'; import { CoreSetup } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { ManagementSetup } from 'src/plugins/management/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { registerFeature } from './register_feature'; export interface PluginsDependencies { @@ -22,20 +22,18 @@ export class TransformUiPlugin { const { management, home } = pluginsSetup; // Register management section - const esSection = management.sections.getSection('elasticsearch'); - if (esSection !== undefined) { - esSection.registerApp({ - id: 'transform', - title: kbnI18n.translate('xpack.transform.appTitle', { - defaultMessage: 'Transforms', - }), - order: 4, - mount: async params => { - const { mountManagementSection } = await import('./app/mount_management_section'); - return mountManagementSection(coreSetup, params); - }, - }); - } + const esSection = management.sections.getSection(ManagementSectionId.Data); + esSection.registerApp({ + id: 'transform', + title: kbnI18n.translate('xpack.transform.appTitle', { + defaultMessage: 'Transforms', + }), + order: 5, + mount: async params => { + const { mountManagementSection } = await import('./app/mount_management_section'); + return mountManagementSection(coreSetup, params); + }, + }); registerFeature(home); } diff --git a/x-pack/plugins/transform/public/register_feature.ts b/x-pack/plugins/transform/public/register_feature.ts index 708dfcb70c67a..c81a18a3def87 100644 --- a/x-pack/plugins/transform/public/register_feature.ts +++ b/x-pack/plugins/transform/public/register_feature.ts @@ -22,7 +22,7 @@ export const registerFeature = (home: HomePublicPluginSetup) => { 'Use transforms to pivot existing Elasticsearch indices into summarized or entity-centric indices.', }), icon: 'managementApp', // there is currently no Transforms icon, so using the general management app icon - path: '/app/kibana#/management/elasticsearch/transform', + path: '/app/kibana#/management/data/transform', showOnHomePage: true, category: FeatureCatalogueCategory.ADMIN, }); diff --git a/x-pack/plugins/transform/server/plugin.ts b/x-pack/plugins/transform/server/plugin.ts index 7da991bc02b37..c8057a3e2fae1 100644 --- a/x-pack/plugins/transform/server/plugin.ts +++ b/x-pack/plugins/transform/server/plugin.ts @@ -6,6 +6,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, + ICustomClusterClient, Plugin, IScopedClusterClient, Logger, @@ -38,10 +39,18 @@ const PLUGIN = { }), }; +async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { + const [core] = await getStartServices(); + return core.elasticsearch.legacy.createClient('transform', { + plugins: [elasticsearchJsPlugin], + }); +} + export class TransformServerPlugin implements Plugin<{}, void, any, any> { private readonly apiRoutes: ApiRoutes; private readonly license: License; private readonly logger: Logger; + private transformESClient?: ICustomClusterClient; constructor(initContext: PluginInitializerContext) { this.logger = initContext.logger.get(); @@ -49,7 +58,7 @@ export class TransformServerPlugin implements Plugin<{}, void, any, any> { this.license = new License(); } - setup({ elasticsearch, http }: CoreSetup, { licensing }: Dependencies): {} { + setup({ http, getStartServices }: CoreSetup, { licensing }: Dependencies): {} { const router = http.createRouter(); this.license.setup( @@ -72,12 +81,11 @@ export class TransformServerPlugin implements Plugin<{}, void, any, any> { }); // Can access via new platform router's handler function 'context' parameter - context.transform.client - const transformClient = elasticsearch.createClient('transform', { - plugins: [elasticsearchJsPlugin], - }); - http.registerRouteHandlerContext('transform', (context, request) => { + http.registerRouteHandlerContext('transform', async (context, request) => { + this.transformESClient = + this.transformESClient ?? (await getCustomEsClient(getStartServices)); return { - dataClient: transformClient.asScoped(request), + dataClient: this.transformESClient.asScoped(request), }; }); @@ -85,5 +93,10 @@ export class TransformServerPlugin implements Plugin<{}, void, any, any> { } start() {} - stop() {} + + stop() { + if (this.transformESClient) { + this.transformESClient.close(); + } + } } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index bc4a1ace2967b..219e922a0158c 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2419,7 +2419,6 @@ "kibana-react.tableListView.listing.table.editActionDescription": "編集", "kibana-react.tableListView.listing.table.editActionName": "編集", "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "{entityName} を削除できません", - "management.connectDataDisplayName": "データに接続", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", "indexPatternManagement.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", @@ -4759,8 +4758,6 @@ "xpack.beatsManagement.breadcrumb.beatTags": "{beatId} のビートタグ", "xpack.beatsManagement.breadcrumb.configurationTags": "構成タグ", "xpack.beatsManagement.breadcrumb.enrolledBeats": "登録済みのビート", - "xpack.beatsManagement.centralManagementLinkLabel": "集中管理", - "xpack.beatsManagement.centralManagementSectionLabel": "ビート", "xpack.beatsManagement.config.other.error": "有効な YAML フォーマットを使用してください", "xpack.beatsManagement.config.otherConfigDescription": "YAML フォーマットで Filebeat インプットの他の設定を指定します", "xpack.beatsManagement.config.otherConfigLabel": "他の構成", @@ -9975,7 +9972,6 @@ "xpack.ml.management.jobsList.noGrantedPrivilegesDescription": "ML ジョブを管理するパーミッションがありません", "xpack.ml.management.jobsList.noPermissionToAccessLabel": "ML ジョブへのアクセスにはパーミッションが必要です", "xpack.ml.management.jobsListTitle": "ジョブリスト", - "xpack.ml.management.mlTitle": "機械学習", "xpack.ml.messagebarService.errorTitle": "エラーが発生しました", "xpack.ml.models.jobService.allOtherRequestsCancelledDescription": " 他のすべてのリクエストはキャンセルされました。", "xpack.ml.models.jobService.categorization.messages.failureToGetTokens": "フィールド値の例のサンプルをトークン化することができませんでした。{message}", @@ -12827,7 +12823,6 @@ "xpack.security.management.roles.statusColumnName": "ステータス", "xpack.security.management.roles.subtitle": "ユーザーのグループにロールを適用してスタック全体のパーミッションを管理", "xpack.security.management.rolesTitle": "ロール", - "xpack.security.management.securityTitle": "セキュリティ", "xpack.security.management.users.confirmDelete.cancelButtonLabel": "キャンセル", "xpack.security.management.users.confirmDelete.confirmButtonLabel": "削除", "xpack.security.management.users.confirmDelete.deleteMultipleUsersTitle": "{userLength} ユーザーの削除", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b22b607f63aeb..954162647bf83 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2420,7 +2420,6 @@ "kibana-react.tableListView.listing.table.editActionDescription": "编辑", "kibana-react.tableListView.listing.table.editActionName": "编辑", "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "无法删除{entityName}", - "management.connectDataDisplayName": "连接数据", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", "indexPatternManagement.editIndexPattern.createIndex.defaultTypeName": "索引模式", @@ -4760,8 +4759,6 @@ "xpack.beatsManagement.breadcrumb.beatTags": "{beatId} 的 Beat 标记", "xpack.beatsManagement.breadcrumb.configurationTags": "配置标记", "xpack.beatsManagement.breadcrumb.enrolledBeats": "已注册 Beats", - "xpack.beatsManagement.centralManagementLinkLabel": "集中管理", - "xpack.beatsManagement.centralManagementSectionLabel": "Beats", "xpack.beatsManagement.config.other.error": "使用有效的 YAML 格式", "xpack.beatsManagement.config.otherConfigDescription": "使用 YAML 格式指定 Filebeat 输入的其他设置", "xpack.beatsManagement.config.otherConfigLabel": "其他配置", @@ -9981,7 +9978,6 @@ "xpack.ml.management.jobsList.noGrantedPrivilegesDescription": "您无权管理 ML 作业", "xpack.ml.management.jobsList.noPermissionToAccessLabel": "您需要访问 ML 作业的权限", "xpack.ml.management.jobsListTitle": "作业列表", - "xpack.ml.management.mlTitle": "Machine Learning", "xpack.ml.messagebarService.errorTitle": "发生了错误", "xpack.ml.models.jobService.allOtherRequestsCancelledDescription": " 所有其他请求已取消。", "xpack.ml.models.jobService.categorization.messages.failureToGetTokens": "无法对示例字段值样本进行分词。{message}", @@ -12834,7 +12830,6 @@ "xpack.security.management.roles.statusColumnName": "状态", "xpack.security.management.roles.subtitle": "将角色应用到用户组并管理整个堆栈的权限。", "xpack.security.management.rolesTitle": "角色", - "xpack.security.management.securityTitle": "安全性", "xpack.security.management.users.confirmDelete.cancelButtonLabel": "取消", "xpack.security.management.users.confirmDelete.confirmButtonLabel": "删除", "xpack.security.management.users.confirmDelete.deleteMultipleUsersTitle": "删除 {userLength} 用户", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 2f5172e8b386a..265cfddab4c06 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -7,7 +7,7 @@ export { BASE_ALERT_API_PATH } from '../../../../alerting/common'; export { BASE_ACTION_API_PATH } from '../../../../actions/common'; -export const BASE_PATH = '/management/kibana/triggersActions'; +export const BASE_PATH = '/management/insightsAndAlerting/triggersActions'; export type Section = 'connectors' | 'alerts'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx index 4f5007949f8b1..1da9abea40dba 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx @@ -127,7 +127,7 @@ describe('connector_add_flyout', () => { const manageLink = callout.find('EuiButton'); expect(manageLink).toHaveLength(1); expect(manageLink.getElements()[0].props.href).toMatchInlineSnapshot( - `"/app/kibana#/management/elasticsearch/license_management/"` + `"/app/kibana#/management/stack/license_management/"` ); const subscriptionLink = callout.find('EuiButtonEmpty'); diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 99a3d65589e8e..016b564c47d00 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -12,7 +12,7 @@ import { registerBuiltInAlertTypes } from './application/components/builtin_aler import { hasShowActionsCapability, hasShowAlertsCapability } from './application/lib/capabilities'; import { ActionTypeModel, AlertTypeModel } from './types'; import { TypeRegistry } from './application/type_registry'; -import { ManagementStart } from '../../../../src/plugins/management/public'; +import { ManagementStart, ManagementSectionId } from '../../../../src/plugins/management/public'; import { boot } from './application/boot'; import { ChartsPluginStart } from '../../../../src/plugins/charts/public'; import { PluginStartContract as AlertingStart } from '../../alerting/public'; @@ -73,12 +73,12 @@ export class Plugin // Don't register routes when user doesn't have access to the application if (canShowActions || canShowAlerts) { - plugins.management.sections.getSection('kibana')!.registerApp({ + plugins.management.sections.getSection(ManagementSectionId.InsightsAndAlerting).registerApp({ id: 'triggersActions', title: i18n.translate('xpack.triggersActionsUI.managementSection.displayName', { defaultMessage: 'Alerts and Actions', }), - order: 7, + order: 0, mount: params => { boot({ dataPlugin: plugins.data, diff --git a/x-pack/plugins/upgrade_assistant/public/plugin.ts b/x-pack/plugins/upgrade_assistant/public/plugin.ts index bb0f21062c151..be00a030d5a27 100644 --- a/x-pack/plugins/upgrade_assistant/public/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/public/plugin.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/public'; import { CloudSetup } from '../../cloud/public'; -import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public'; import { NEXT_MAJOR_VERSION } from '../common/version'; import { Config } from '../common/config'; @@ -24,7 +24,7 @@ export class UpgradeAssistantUIPlugin implements Plugin { if (!enabled) { return; } - const appRegistrar = management.sections.getSection('elasticsearch')!; + const appRegistrar = management.sections.getSection(ManagementSectionId.Stack); const isCloudEnabled = Boolean(cloud?.isCloudEnabled); appRegistrar.registerApp({ @@ -33,7 +33,7 @@ export class UpgradeAssistantUIPlugin implements Plugin { defaultMessage: '{version} Upgrade Assistant', values: { version: `${NEXT_MAJOR_VERSION}.0` }, }), - order: 1000, + order: 1, async mount(params) { const { mountManagementSection } = await import('./application/mount_management_section'); return mountManagementSection(coreSetup, isCloudEnabled, params); diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap index fb40a42e47f75..5ae90a1613575 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/license_info.test.tsx.snap @@ -28,7 +28,7 @@ Array [

@@ -64,7 +64,7 @@ exports[`ShowLicenseInfo shallow renders without errors 1`] = `

Start free 14-day trial diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap index 3ee949b9712bd..df9de8c2ad03d 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/ml/__tests__/__snapshots__/ml_flyout.test.tsx.snap @@ -160,7 +160,7 @@ exports[`ML Flyout component shows license info if no ml available 1`] = `

diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx index fae81177a728c..33fbbb117a11a 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ml/license_info.tsx @@ -22,7 +22,7 @@ export const ShowLicenseInfo = () => {

{labels.START_TRAIL_DESC}

{labels.START_TRAIL} diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx index cba96cd2fe5b1..0cdb3c0feb71f 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx @@ -61,7 +61,7 @@ export const ToggleAlertFlyoutButtonComponent: React.FC = ({ - {indexPatternMissing && ( + {indexPatternMissing && !loading && ( ', () => { expect(findTestSubject(idColumn, `watchIdColumn-${watch1.id}`).length).toBe(1); expect(findTestSubject(idColumn, `watchIdColumn-${watch1.id}`).props().href).toEqual( - `#/management/elasticsearch/watcher/watches/watch/${watch1.id}/status` + `#/management/insightsAndAlerting/watcher/watches/watch/${watch1.id}/status` ); }); diff --git a/x-pack/plugins/watcher/public/application/app.tsx b/x-pack/plugins/watcher/public/application/app.tsx index f4b9441719386..8a6d2746237e9 100644 --- a/x-pack/plugins/watcher/public/application/app.tsx +++ b/x-pack/plugins/watcher/public/application/app.tsx @@ -69,7 +69,7 @@ export const App = (deps: AppDeps) => { iconType="help" > {message}{' '} - + { values={{ watchName: watch.name, watchStatusLink: ( - + { return ( {id} @@ -326,7 +326,7 @@ export const WatchList = () => { )} iconType="pencil" color="primary" - href={`#/management/elasticsearch/watcher/watches/watch/${watch.id}/edit`} + href={`#/management/insightsAndAlerting/watcher/watches/watch/${watch.id}/edit`} data-test-subj="editWatchButton" /> diff --git a/x-pack/plugins/watcher/public/plugin.ts b/x-pack/plugins/watcher/public/plugin.ts index 6de21bc27d48c..6496c742fcb40 100644 --- a/x-pack/plugins/watcher/public/plugin.ts +++ b/x-pack/plugins/watcher/public/plugin.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Plugin, CoreStart } from 'kibana/public'; import { first, map, skip } from 'rxjs/operators'; +import { ManagementSectionId } from '../../../../src/plugins/management/public'; import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; import { LicenseStatus } from '../common/types/license_status'; @@ -28,14 +29,15 @@ export class WatcherUIPlugin implements Plugin { { notifications, http, uiSettings, getStartServices }: CoreSetup, { licensing, management, data, home, charts }: Dependencies ) { - const esSection = management.sections.getSection('elasticsearch'); + const esSection = management.sections.getSection(ManagementSectionId.InsightsAndAlerting); - const watcherESApp = esSection!.registerApp({ + const watcherESApp = esSection.registerApp({ id: 'watcher', title: i18n.translate( 'xpack.watcher.sections.watchList.managementSection.watcherDisplayName', { defaultMessage: 'Watcher' } ), + order: 3, mount: async ({ element, setBreadcrumbs }) => { const [core] = await getStartServices(); const { i18n: i18nDep, docLinks, savedObjects } = core; @@ -74,7 +76,7 @@ export class WatcherUIPlugin implements Plugin { defaultMessage: 'Detect changes in your data by creating, managing, and monitoring alerts.', }), icon: 'watchesApp', - path: '/app/kibana#/management/elasticsearch/watcher/watches', + path: '/app/kibana#/management/insightsAndAlerting/watcher/watches', showOnHomePage: false, }; diff --git a/x-pack/plugins/watcher/server/plugin.ts b/x-pack/plugins/watcher/server/plugin.ts index 6a2e3b2e596b6..3f2891f919e37 100644 --- a/x-pack/plugins/watcher/server/plugin.ts +++ b/x-pack/plugins/watcher/server/plugin.ts @@ -12,6 +12,7 @@ declare module 'kibana/server' { import { CoreSetup, + ICustomClusterClient, IScopedClusterClient, Logger, Plugin, @@ -33,8 +34,15 @@ export interface WatcherContext { client: IScopedClusterClient; } +async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { + const [core] = await getStartServices(); + const esConfig = { plugins: [elasticsearchJsPlugin] }; + return core.elasticsearch.legacy.createClient('watcher', esConfig); +} + export class WatcherServerPlugin implements Plugin { - log: Logger; + private readonly log: Logger; + private watcherESClient?: ICustomClusterClient; private licenseStatus: LicenseStatus = { hasRequired: false, @@ -44,21 +52,17 @@ export class WatcherServerPlugin implements Plugin { this.log = ctx.logger.get(); } - async setup( - { http, elasticsearch: elasticsearchService }: CoreSetup, - { licensing }: Dependencies - ) { + async setup({ http, getStartServices }: CoreSetup, { licensing }: Dependencies) { const router = http.createRouter(); const routeDependencies: RouteDependencies = { router, getLicenseStatus: () => this.licenseStatus, }; - const config = { plugins: [elasticsearchJsPlugin] }; - const watcherESClient = elasticsearchService.createClient('watcher', config); - http.registerRouteHandlerContext('watcher', (ctx, request) => { + http.registerRouteHandlerContext('watcher', async (ctx, request) => { + this.watcherESClient = this.watcherESClient ?? (await getCustomEsClient(getStartServices)); return { - client: watcherESClient.asScoped(request), + client: this.watcherESClient.asScoped(request), }; }); @@ -89,6 +93,12 @@ export class WatcherServerPlugin implements Plugin { } }); } + start() {} - stop() {} + + stop() { + if (this.watcherESClient) { + this.watcherESClient.close(); + } + } } diff --git a/x-pack/test/api_integration/apis/ml/job_validation/validate.ts b/x-pack/test/api_integration/apis/ml/job_validation/validate.ts index aaeead57345bc..9695c2b6892e0 100644 --- a/x-pack/test/api_integration/apis/ml/job_validation/validate.ts +++ b/x-pack/test/api_integration/apis/ml/job_validation/validate.ts @@ -6,6 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/machine_learning/security_common'; +import pkg from '../../../../../../package.json'; const COMMON_HEADERS = { 'kbn-xsrf': 'some-xsrf-token', @@ -74,15 +75,14 @@ export default ({ getService }: FtrProviderContext) => { heading: 'Job ID format is valid', text: 'Lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores, starts and ends with an alphanumeric character, and is no more than 64 characters long.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/master/ml-job-resource.html#ml-job-resource', + url: `https://www.elastic.co/guide/en/elasticsearch/reference/${pkg.branch}/ml-job-resource.html#ml-job-resource`, status: 'success', }, { id: 'detectors_function_not_empty', heading: 'Detector functions', text: 'Presence of detector functions validated in all detectors.', - url: 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#detectors', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#detectors`, status: 'success', }, { @@ -90,8 +90,7 @@ export default ({ getService }: FtrProviderContext) => { bucketSpan: '15m', heading: 'Bucket span', text: 'Format of "15m" is valid and passed validation checks.', - url: - 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#bucket-span', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#bucket-span`, status: 'success', }, { @@ -104,8 +103,7 @@ export default ({ getService }: FtrProviderContext) => { id: 'success_mml', heading: 'Model memory limit', text: 'Valid and within the estimated model memory limit.', - url: - 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#model-memory-limits', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#model-memory-limits`, status: 'success', }, ]); @@ -157,15 +155,14 @@ export default ({ getService }: FtrProviderContext) => { id: 'job_id_invalid', text: 'Job ID is invalid. It can contain lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores and must start and end with an alphanumeric character.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/master/ml-job-resource.html#ml-job-resource', + url: `https://www.elastic.co/guide/en/elasticsearch/reference/${pkg.branch}/ml-job-resource.html#ml-job-resource`, status: 'error', }, { id: 'detectors_function_not_empty', heading: 'Detector functions', text: 'Presence of detector functions validated in all detectors.', - url: 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#detectors', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#detectors`, status: 'success', }, { @@ -173,8 +170,7 @@ export default ({ getService }: FtrProviderContext) => { bucketSpan: '15m', heading: 'Bucket span', text: 'Format of "15m" is valid.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/master/ml-job-resource.html#ml-analysisconfig', + url: `https://www.elastic.co/guide/en/elasticsearch/reference/${pkg.branch}/ml-job-resource.html#ml-analysisconfig`, status: 'success', }, { @@ -239,15 +235,14 @@ export default ({ getService }: FtrProviderContext) => { heading: 'Job ID format is valid', text: 'Lowercase alphanumeric (a-z and 0-9) characters, hyphens or underscores, starts and ends with an alphanumeric character, and is no more than 64 characters long.', - url: - 'https://www.elastic.co/guide/en/elasticsearch/reference/master/ml-job-resource.html#ml-job-resource', + url: `https://www.elastic.co/guide/en/elasticsearch/reference/${pkg.branch}/ml-job-resource.html#ml-job-resource`, status: 'success', }, { id: 'detectors_function_not_empty', heading: 'Detector functions', text: 'Presence of detector functions validated in all detectors.', - url: 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#detectors', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#detectors`, status: 'success', }, { @@ -262,8 +257,7 @@ export default ({ getService }: FtrProviderContext) => { fieldName: 'order_id', text: 'Cardinality of partition_field "order_id" is above 1000 and might result in high memory usage.', - url: - 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#cardinality', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#cardinality`, status: 'warning', }, { @@ -271,8 +265,7 @@ export default ({ getService }: FtrProviderContext) => { heading: 'Bucket span', text: 'Bucket span is 1 day or more. Be aware that days are considered as UTC days, not local days.', - url: - 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#bucket-span', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#bucket-span`, status: 'info', }, { @@ -287,7 +280,7 @@ export default ({ getService }: FtrProviderContext) => { { id: 'success_influencers', text: 'Influencer configuration passed the validation checks.', - url: 'https://www.elastic.co/guide/en/machine-learning/master/ml-influencers.html', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/ml-influencers.html`, status: 'success', }, { @@ -295,8 +288,7 @@ export default ({ getService }: FtrProviderContext) => { mml: '1MB', text: 'The specified model memory limit is less than half of the estimated model memory limit and will likely hit the hard limit.', - url: - 'https://www.elastic.co/guide/en/machine-learning/master/create-jobs.html#model-memory-limits', + url: `https://www.elastic.co/guide/en/machine-learning/${pkg.branch}/create-jobs.html#model-memory-limits`, status: 'warning', }, ]); diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index 39c87a91f0ccf..54ccbb5e0cbbd 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -236,6 +236,9 @@ export default ({ getService }: FtrProviderContext) => { const datafeedId = `datafeed-${job.jobId}`; await ml.api.waitForAnomalyDetectionJobToExist(job.jobId); await ml.api.waitForDatafeedToExist(datafeedId); + if (testData.requestBody.startDatafeed === true) { + await ml.api.waitForADJobRecordCountToBePositive(job.jobId); + } await ml.api.waitForJobState(job.jobId, job.jobState); await ml.api.waitForDatafeedState(datafeedId, job.datafeedState); } diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 082008bccddd1..5dd9364c48fda 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -72,7 +72,8 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { .perform(); } - describe('lens smokescreen tests', () => { + // Failing: https://github.com/elastic/kibana/issues/66779 + describe.skip('lens smokescreen tests', () => { it('should allow editing saved visualizations', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens'); diff --git a/x-pack/test/functional/apps/reporting_management/report_delete_pagination.ts b/x-pack/test/functional/apps/reporting_management/report_delete_pagination.ts index d9d387f89640a..8c96fdbd0cbef 100644 --- a/x-pack/test/functional/apps/reporting_management/report_delete_pagination.ts +++ b/x-pack/test/functional/apps/reporting_management/report_delete_pagination.ts @@ -21,7 +21,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await security.testUser.setRoles(['kibana_admin', 'reporting_user']); await esArchiver.load('empty_kibana'); await esArchiver.load('reporting/archived_reports'); - await pageObjects.common.navigateToActualUrl('kibana', '/management/kibana/reporting'); + await pageObjects.common.navigateToApp('reporting'); await testSubjects.existOrFail('reportJobListing', { timeout: 200000 }); }); diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 0639782dc7f7b..94976511a1b12 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -114,7 +114,7 @@ export default async function({ readConfigFile }) { }, logstashPipelines: { pathname: '/app/kibana', - hash: '/management/logstash/pipelines', + hash: '/management/ingest/pipelines', }, maps: { pathname: '/app/maps', @@ -155,7 +155,7 @@ export default async function({ readConfigFile }) { }, rollupJob: { pathname: '/app/kibana', - hash: '/management/elasticsearch/rollup_jobs/', + hash: '/management/data/rollup_jobs/', }, apiKeys: { pathname: '/app/kibana', @@ -163,46 +163,46 @@ export default async function({ readConfigFile }) { }, licenseManagement: { pathname: '/app/kibana', - hash: '/management/elasticsearch/license_management', + hash: '/management/stack/license_management', }, indexManagement: { pathname: '/app/kibana', - hash: '/management/elasticsearch/index_management', + hash: '/management/data/index_management', }, indexLifecycleManagement: { pathname: '/app/kibana', - hash: '/management/elasticsearch/index_lifecycle_management', + hash: '/management/data/index_lifecycle_management', }, ingestPipelines: { pathname: '/app/kibana', - hash: '/management/elasticsearch/ingest_pipelines', + hash: '/management/ingest/ingest_pipelines', }, snapshotRestore: { pathname: '/app/kibana', - hash: '/management/elasticsearch/snapshot_restore', + hash: '/management/data/snapshot_restore', }, crossClusterReplication: { pathname: '/app/kibana', - hash: '/management/elasticsearch/cross_cluster_replication', + hash: '/management/data/cross_cluster_replication', }, remoteClusters: { pathname: '/app/kibana', - hash: '/management/elasticsearch/remote_clusters', + hash: '/management/data/remote_clusters', }, apm: { pathname: '/app/apm', }, watcher: { pathname: '/app/kibana', - hash: '/management/elasticsearch/watcher/watches/', + hash: '/management/insightsAndAlerting/watcher/watches/', }, transform: { pathname: '/app/kibana/', - hash: '/management/elasticsearch/transform', + hash: '/management/data/transform', }, reporting: { pathname: '/app/kibana/', - hash: '/management/kibana/reporting', + hash: '/management/insightsAndAlerting/reporting', }, }, diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index cb3598652a39a..d313fd2046c03 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -233,7 +233,7 @@ "title" : "document example top hits", "description" : "", "mapStateJSON" : "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-24T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"\",\"language\":\"kuery\"}}", - "layerListJSON" : "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"topHitsTimeField\":\"@timestamp\",\"useTopHits\":true,\"topHitsSplitField\":\"machine.os.raw\",\"topHitsSize\":2,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]", + "layerListJSON" : "[{\"id\":\"z52lq\",\"label\":\"logstash\",\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[],\"topHitsTimeField\":\"@timestamp\",\"useTopHits\":true,\"topHitsSplitField\":\"machine.os.raw\",\"topHitsSize\":2,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"previousStyle\":null},\"type\":\"VECTOR\"}]", "uiStateJSON" : "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}", "bounds" : { "type" : "polygon", @@ -467,7 +467,7 @@ "type": "envelope" }, "description": "", - "layerListJSON" : "[{\"id\":\"0hmz5\",\"label\":\"EMS base layer (road_map)\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"VECTOR_TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"applyGlobalQuery\":false,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3},\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_1_join_0_index_pattern\"}}]}]", + "layerListJSON" : "[{\"id\":\"n1t6f\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"62eca1fc-fe42-11e8-8eb2-f2801f1b9fd1\",\"type\":\"ES_SEARCH\",\"geoField\":\"geometry\",\"limit\":2048,\"filterByMapBounds\":false,\"showTooltip\":true,\"tooltipProperties\":[\"name\"],\"applyGlobalQuery\":false,\"indexPatternRefName\":\"layer_1_source_index_pattern\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3},\"field\":{\"label\":\"max(prop1) group by meta_for_geo_shapes*.shape_name\",\"name\":\"__kbnjoin__max_of_prop1_groupby_meta_for_geo_shapes*.shape_name\",\"origin\":\"join\"},\"color\":\"Blues\"}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"id\":\"855ccb86-fe42-11e8-8eb2-f2801f1b9fd1\",\"indexPatternTitle\":\"meta_for_geo_shapes*\",\"term\":\"shape_name\",\"metrics\":[{\"type\":\"max\",\"field\":\"prop1\"}],\"applyGlobalQuery\":true,\"indexPatternRefName\":\"layer_1_join_0_index_pattern\"}}]}]", "mapStateJSON": "{\"zoom\":3.02,\"center\":{\"lon\":77.33426,\"lat\":-0.04647},\"timeFilters\":{\"from\":\"now-17m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}", "title": "join example", "uiStateJSON": "{\"isLayerTOCOpen\":true,\"openTOCDetails\":[\"n1t6f\"]}" @@ -512,7 +512,7 @@ ], "type": "envelope" }, - "layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"3xlvm\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"427aa49d-a552-4e7d-a629-67c47db27128\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"heatmap\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"HEATMAP\",\"refinement\":\"coarse\",\"properties\":{},\"previousStyle\":null},\"type\":\"HEATMAP\"}]", + "layerListJSON": "[{\"id\":\"3xlvm\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"427aa49d-a552-4e7d-a629-67c47db27128\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"heatmap\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"HEATMAP\",\"refinement\":\"coarse\",\"properties\":{},\"previousStyle\":null},\"type\":\"HEATMAP\"}]", "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}", "title": "geo grid heatmap example", "uiStateJSON": "{\"isDarkMode\":false}" @@ -543,7 +543,7 @@ "type": "envelope" }, "description": "", - "layerListJSON": "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]", + "layerListJSON": "[{\"id\":\"g1xkv\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"resolution\": \"COARSE\",\"type\":\"ES_GEO_GRID\",\"id\":\"9305f6ea-4518-4c06-95b9-33321aa38d6a\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"requestType\":\"grid\",\"metrics\":[{\"type\":\"count\"},{\"type\":\"max\",\"field\":\"bytes\"}]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"max of bytes\",\"name\":\"max_of_bytes\",\"origin\":\"source\"},\"color\":\"Blues\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]", "mapStateJSON": "{\"zoom\":3.59,\"center\":{\"lon\":-98.05765,\"lat\":38.32288},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000}}", "title": "geo grid vector grid example", "uiStateJSON": "{\"isDarkMode\":false}" diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts index afc2567f3cce9..6fdc268810036 100644 --- a/x-pack/test/functional/services/machine_learning/api.ts +++ b/x-pack/test/functional/services/machine_learning/api.ts @@ -138,11 +138,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, async getJobState(jobId: string): Promise { - log.debug(`Fetching job state for job ${jobId}`); - const jobStats = await esSupertest - .get(`/_ml/anomaly_detectors/${jobId}/_stats`) - .expect(200) - .then((res: any) => res.body); + const jobStats = await this.getADJobStats(jobId); expect(jobStats.jobs).to.have.length(1); const state: JOB_STATE = jobStats.jobs[0].state; @@ -150,6 +146,16 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { return state; }, + async getADJobStats(jobId: string): Promise { + log.debug(`Fetching anomaly detection job stats for job ${jobId}...`); + const jobStats = await esSupertest + .get(`/_ml/anomaly_detectors/${jobId}/_stats`) + .expect(200) + .then((res: any) => res.body); + + return jobStats; + }, + async waitForJobState(jobId: string, expectedJobState: JOB_STATE) { await retry.waitForWithTimeout( `job state to be ${expectedJobState}`, @@ -390,5 +396,31 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { await this.waitForDataFrameAnalyticsJobToExist(analyticsId); }, + + async getADJobRecordCount(jobId: string): Promise { + const jobStats = await this.getADJobStats(jobId); + + expect(jobStats.jobs).to.have.length(1); + const processedRecordCount: number = jobStats.jobs[0].data_counts.processed_record_count; + + return processedRecordCount; + }, + + async waitForADJobRecordCountToBePositive(jobId: string) { + await retry.waitForWithTimeout( + `'${jobId}' to have processed_record_count > 0`, + 10 * 1000, + async () => { + const processedRecordCount = await this.getADJobRecordCount(jobId); + if (processedRecordCount > 0) { + return true; + } else { + throw new Error( + `expected anomaly detection job '${jobId}' to have processed_record_count > 0 (got ${processedRecordCount})` + ); + } + } + ); + }, }; } diff --git a/x-pack/test/functional/services/machine_learning/job_management.ts b/x-pack/test/functional/services/machine_learning/job_management.ts index 8b85f85d46cf4..085bb31258012 100644 --- a/x-pack/test/functional/services/machine_learning/job_management.ts +++ b/x-pack/test/functional/services/machine_learning/job_management.ts @@ -43,6 +43,7 @@ export function MachineLearningJobManagementProvider( }, async waitForJobCompletion(jobId: string) { + await mlApi.waitForADJobRecordCountToBePositive(jobId); await mlApi.waitForDatafeedState(`datafeed-${jobId}`, DATAFEED_STATE.STOPPED); await mlApi.waitForJobState(jobId, JOB_STATE.CLOSED); }, diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index 37cc71d6865b0..d372bd53c081b 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -65,8 +65,8 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv }, goToCertificates: async () => { - await testSubjects.click('uptimeCertificatesLink', 10000); - return retry.tryForTime(60 * 1000, async () => { + return retry.try(async () => { + await testSubjects.click('uptimeCertificatesLink'); await testSubjects.existOrFail('uptimeCertificatesPage'); }); }, diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts index 50de76d67e06b..f69c17c96f074 100644 --- a/x-pack/test/functional_with_es_ssl/config.ts +++ b/x-pack/test/functional_with_es_ssl/config.ts @@ -51,7 +51,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { ...xpackFunctionalConfig.get('apps'), triggersActions: { pathname: '/app/kibana', - hash: '/management/kibana/triggersActions', + hash: '/management/insightsAndAlerting/triggersActions', }, }, esTestCluster: { diff --git a/yarn.lock b/yarn.lock index 245e0de152658..a18f89cd480f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1427,10 +1427,10 @@ "@types/node-jose" "1.1.0" node-jose "1.1.0" -"@elastic/static-fs@1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@elastic/static-fs/-/static-fs-1.0.1.tgz#2e084e9fc321dd4c7fb4579021ca8a6b26f3464e" - integrity sha512-Vl40Va/h0P6aDUrzgDeTabGVUb/s/W92le64E1UXTcG5927cZtTnOu0datMjr98xdr9C6RAJ3mr6zgxfox5TNw== +"@elastic/static-fs@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@elastic/static-fs/-/static-fs-1.0.2.tgz#c1e5fea6a1b35abcd005cecf7880156ed0f273ae" + integrity sha512-0cZc5D9Wg6pJsc8Sa2ns1eOuxtXEidE7GBb2B0KZdJq9nZzUCxMyplURqT0Nr3i5XpoHb6ZEmxWsji86j1KjDw== "@elastic/ui-ace@0.2.3": version "0.2.3"