diff --git a/docs/development/core/public/kibana-plugin-public.savedobject.md b/docs/development/core/public/kibana-plugin-public.savedobject.md index b1bb3e267bf0e..542d7d3a063ec 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobject.md +++ b/docs/development/core/public/kibana-plugin-public.savedobject.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface SavedObject +export interface SavedObject ``` ## Properties diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsbatchresponse.md b/docs/development/core/public/kibana-plugin-public.savedobjectsbatchresponse.md index 5a08b3f97f429..97772112ff006 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsbatchresponse.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsbatchresponse.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface SavedObjectsBatchResponse +export interface SavedObjectsBatchResponse ``` ## Properties diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsbulkcreateobject.md b/docs/development/core/public/kibana-plugin-public.savedobjectsbulkcreateobject.md index c479e9f9f3e3f..ef83acb8fd73d 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsbulkcreateobject.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsbulkcreateobject.md @@ -7,7 +7,7 @@ Signature: ```typescript -export interface SavedObjectsBulkCreateObject extends SavedObjectsCreateOptions +export interface SavedObjectsBulkCreateObject extends SavedObjectsCreateOptions ``` ## Properties diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsbulkupdateobject.md b/docs/development/core/public/kibana-plugin-public.savedobjectsbulkupdateobject.md index ca0eabb265901..a57702c305f50 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsbulkupdateobject.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsbulkupdateobject.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface SavedObjectsBulkUpdateObject +export interface SavedObjectsBulkUpdateObject ``` ## Properties diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkcreate.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkcreate.md index 391b2f57205d5..c5072bea5b50e 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkcreate.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkcreate.md @@ -9,5 +9,5 @@ Creates multiple documents at once Signature: ```typescript -bulkCreate: (objects?: SavedObjectsBulkCreateObject[], options?: SavedObjectsBulkCreateOptions) => Promise>; +bulkCreate: (objects?: SavedObjectsBulkCreateObject[], options?: SavedObjectsBulkCreateOptions) => Promise>; ``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkget.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkget.md index a54dfe72167a7..37b9f6d6c951d 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkget.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkget.md @@ -12,7 +12,7 @@ Returns an array of objects by id bulkGet: (objects?: { id: string; type: string; - }[]) => Promise>; + }[]) => Promise>; ``` ## Example diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkupdate.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkupdate.md index 94ae9bb70ccda..2f8565def3d40 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkupdate.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.bulkupdate.md @@ -9,7 +9,7 @@ Update multiple documents at once Signature: ```typescript -bulkUpdate(objects?: SavedObjectsBulkUpdateObject[]): Promise>; +bulkUpdate(objects?: SavedObjectsBulkUpdateObject[]): Promise>; ``` ## Parameters @@ -20,7 +20,7 @@ bulkUpdate(objects?: SavedObjectsBulkUpdateObje Returns: -`Promise>` +`Promise>` The result of the update operation containing both failed and updated saved objects. diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.create.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.create.md index 5a7666084ea0f..ea3fbe31ca8a5 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.create.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.create.md @@ -9,5 +9,5 @@ Persists an object Signature: ```typescript -create: (type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise>; +create: (type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise>; ``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md index d3494045952ad..3d8005c9390b7 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md @@ -9,5 +9,5 @@ Search for objects Signature: ```typescript -find: (options: Pick) => Promise>; +find: (options: Pick) => Promise>; ``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.get.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.get.md index bddbadd3e1361..37a91f7211da5 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.get.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.get.md @@ -9,5 +9,5 @@ Fetches a single object Signature: ```typescript -get: (type: string, id: string) => Promise>; +get: (type: string, id: string) => Promise>; ``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md index 7aa17eae2da87..5c22a2a0bdd91 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md @@ -20,12 +20,12 @@ The constructor for this class is marked as internal. Third-party code should no | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [bulkCreate](./kibana-plugin-public.savedobjectsclient.bulkcreate.md) | | (objects?: SavedObjectsBulkCreateObject<SavedObjectAttributes>[], options?: SavedObjectsBulkCreateOptions) => Promise<SavedObjectsBatchResponse<SavedObjectAttributes>> | Creates multiple documents at once | -| [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | (objects?: {
id: string;
type: string;
}[]) => Promise<SavedObjectsBatchResponse<SavedObjectAttributes>> | Returns an array of objects by id | -| [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>> | Persists an object | +| [bulkCreate](./kibana-plugin-public.savedobjectsclient.bulkcreate.md) | | (objects?: SavedObjectsBulkCreateObject<unknown>[], options?: SavedObjectsBulkCreateOptions) => Promise<SavedObjectsBatchResponse<unknown>> | Creates multiple documents at once | +| [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | (objects?: {
id: string;
type: string;
}[]) => Promise<SavedObjectsBatchResponse<unknown>> | Returns an array of objects by id | +| [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <T = unknown>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>> | Persists an object | | [delete](./kibana-plugin-public.savedobjectsclient.delete.md) | | (type: string, id: string) => Promise<{}> | Deletes an object | -| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "perPage" | "sortField" | "fields" | "searchFields" | "hasReference" | "defaultSearchOperator">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | -| [get](./kibana-plugin-public.savedobjectsclient.get.md) | | <T extends SavedObjectAttributes>(type: string, id: string) => Promise<SimpleSavedObject<T>> | Fetches a single object | +| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T = unknown>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "page" | "perPage" | "sortField" | "fields" | "searchFields" | "hasReference" | "defaultSearchOperator">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | +| [get](./kibana-plugin-public.savedobjectsclient.get.md) | | <T = unknown>(type: string, id: string) => Promise<SimpleSavedObject<T>> | Fetches a single object | ## Methods diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.update.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.update.md index 9f7e46943bbd5..d1049a75edf1f 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.update.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.update.md @@ -9,7 +9,7 @@ Updates an object Signature: ```typescript -update(type: string, id: string, attributes: T, { version, migrationVersion, references }?: SavedObjectsUpdateOptions): Promise>; +update(type: string, id: string, attributes: T, { version, migrationVersion, references }?: SavedObjectsUpdateOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsfindresponsepublic.md b/docs/development/core/public/kibana-plugin-public.savedobjectsfindresponsepublic.md index f60e7305fba34..31ab73464dfd9 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsfindresponsepublic.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsfindresponsepublic.md @@ -11,7 +11,7 @@ Return type of the Saved Objects `find()` method. Signature: ```typescript -export interface SavedObjectsFindResponsePublic extends SavedObjectsBatchResponse +export interface SavedObjectsFindResponsePublic extends SavedObjectsBatchResponse ``` ## Properties diff --git a/docs/development/core/public/kibana-plugin-public.simplesavedobject.md b/docs/development/core/public/kibana-plugin-public.simplesavedobject.md index 1f6de163ec17d..4906d5967f7df 100644 --- a/docs/development/core/public/kibana-plugin-public.simplesavedobject.md +++ b/docs/development/core/public/kibana-plugin-public.simplesavedobject.md @@ -11,7 +11,7 @@ It provides basic functionality for creating/saving/deleting saved objects, but Signature: ```typescript -export declare class SimpleSavedObject +export declare class SimpleSavedObject ``` ## Constructors diff --git a/docs/development/core/server/kibana-plugin-server.savedobject.md b/docs/development/core/server/kibana-plugin-server.savedobject.md index b3184fd38ad93..5ab08f4eadf2e 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobject.md +++ b/docs/development/core/server/kibana-plugin-server.savedobject.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface SavedObject +export interface SavedObject ``` ## Properties diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkcreateobject.md b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkcreateobject.md index 87386b986009d..7b765055de6c1 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkcreateobject.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkcreateobject.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface SavedObjectsBulkCreateObject +export interface SavedObjectsBulkCreateObject ``` ## Properties diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkresponse.md b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkresponse.md index 20a1194c87eda..2ced4f4c8e1a0 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkresponse.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkresponse.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface SavedObjectsBulkResponse +export interface SavedObjectsBulkResponse ``` ## Properties diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateobject.md b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateobject.md index 8e4e3d761148e..67013290a629d 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateobject.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateobject.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface SavedObjectsBulkUpdateObject extends Pick +export interface SavedObjectsBulkUpdateObject extends Pick ``` ## Properties diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateresponse.md b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateresponse.md index 065b9df0823cd..3468991611608 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateresponse.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateresponse.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface SavedObjectsBulkUpdateResponse +export interface SavedObjectsBulkUpdateResponse ``` ## Properties diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkcreate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkcreate.md index 40f947188de54..47da795631a3a 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkcreate.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkcreate.md @@ -9,7 +9,7 @@ Persists multiple documents batched together as a single request Signature: ```typescript -bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; +bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkget.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkget.md index c86c30d14db3b..71006e2afa3ee 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkget.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkget.md @@ -9,7 +9,7 @@ Returns an array of objects by id Signature: ```typescript -bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; +bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md index 33958837ebca3..cceeb9c1ca320 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md @@ -9,7 +9,7 @@ Bulk Updates multiple SavedObject at once Signature: ```typescript -bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; +bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.create.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.create.md index ddb78a57e71bc..7f6fc117937cb 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.create.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.create.md @@ -9,7 +9,7 @@ Persists a SavedObject Signature: ```typescript -create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; +create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.find.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.find.md index f72691d3ce0c8..c8804e2a97851 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.find.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.find.md @@ -9,7 +9,7 @@ Find all SavedObjects matching the search query Signature: ```typescript -find(options: SavedObjectsFindOptions): Promise>; +find(options: SavedObjectsFindOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.get.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.get.md index 3906462184d4f..b48cef25ca3d1 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.get.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.get.md @@ -9,7 +9,7 @@ Retrieves a single object Signature: ```typescript -get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; +get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.update.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.update.md index 2c71e518b7b05..ed6243c409fa4 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.update.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.update.md @@ -9,7 +9,7 @@ Updates an SavedObject Signature: ```typescript -update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; +update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsfindresponse.md b/docs/development/core/server/kibana-plugin-server.savedobjectsfindresponse.md index efdc07cea88fd..a79a23db967cc 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsfindresponse.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsfindresponse.md @@ -11,7 +11,7 @@ Return type of the Saved Objects `find()` method. Signature: ```typescript -export interface SavedObjectsFindResponse +export interface SavedObjectsFindResponse ``` ## Properties diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md index dfe9e51e62483..f0d8d9edfbe79 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md @@ -9,7 +9,7 @@ Creates multiple documents at once Signature: ```typescript -bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; +bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md index 34b113bce5410..e27c5fc3bec9a 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md @@ -9,7 +9,7 @@ Returns an array of objects by id Signature: ```typescript -bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; +bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md index 23c7a92624957..5ad09d59f4061 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md @@ -9,7 +9,7 @@ Updates multiple objects in bulk Signature: ```typescript -bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; +bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md index 29e3c3ab24654..fd6495bd2d3c4 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md @@ -9,7 +9,7 @@ Persists an object Signature: ```typescript -create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; +create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md index dbf6d59e78d85..ccb9feca1669e 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md @@ -7,7 +7,7 @@ Signature: ```typescript -find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; +find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md index 930a4647ca175..b3ccbc7277b4e 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md @@ -9,7 +9,7 @@ Gets a single object Signature: ```typescript -get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; +get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md index 5e9f69ecc567b..bb215cdb97af5 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md @@ -9,7 +9,7 @@ Updates an object Signature: ```typescript -update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; +update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsupdateresponse.md b/docs/development/core/server/kibana-plugin-server.savedobjectsupdateresponse.md index 64c9037735358..130b0b4faaa07 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsupdateresponse.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsupdateresponse.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface SavedObjectsUpdateResponse extends Omit, 'attributes' | 'references'> +export interface SavedObjectsUpdateResponse extends Omit, 'attributes' | 'references'> ``` ## Properties diff --git a/packages/kbn-plugin-generator/index.js b/packages/kbn-plugin-generator/index.js index 90274288357b8..15adce7f01c8e 100644 --- a/packages/kbn-plugin-generator/index.js +++ b/packages/kbn-plugin-generator/index.js @@ -29,6 +29,7 @@ exports.run = function run(argv) { const options = getopts(argv, { alias: { h: 'help', + i: 'internal', }, }); @@ -40,17 +41,22 @@ exports.run = function run(argv) { if (options.help) { console.log( dedent(chalk` - {dim usage:} node scripts/generate-plugin {bold [name]} - - generate a fresh Kibana plugin in the plugins/ directory + # {dim Usage:} + node scripts/generate-plugin {bold [name]} + Generate a fresh Kibana plugin in the plugins/ directory + + # {dim Core Kibana plugins:} + node scripts/generate-plugin {bold [name]} -i + To generate a core Kibana plugin inside the src/plugins/ directory, add the -i flag. `) + '\n' ); process.exit(1); } const name = options._[0]; + const isKibanaPlugin = options.internal; const template = resolve(__dirname, './sao_template'); - const kibanaPlugins = resolve(__dirname, '../../plugins'); + const kibanaPlugins = resolve(__dirname, isKibanaPlugin ? '../../src/plugins' : '../../plugins'); const targetPath = resolve(kibanaPlugins, snakeCase(name)); sao({ @@ -58,6 +64,8 @@ exports.run = function run(argv) { targetPath: targetPath, configOptions: { name, + isKibanaPlugin, + targetPath, }, }).catch(error => { console.error(chalk`{red fatal error}!`); diff --git a/packages/kbn-plugin-generator/index.js.d.ts b/packages/kbn-plugin-generator/index.js.d.ts new file mode 100644 index 0000000000000..46f7c43fd5790 --- /dev/null +++ b/packages/kbn-plugin-generator/index.js.d.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ +interface PluginGenerator { + /** + * Run plugin generator. + */ + run: (...args: any[]) => any; +} diff --git a/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js b/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js index f7752883e0464..771bf43c4020a 100644 --- a/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js +++ b/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js @@ -61,7 +61,8 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug expect(stats.isDirectory()).toBe(true); }); - it(`should create an internationalization config file with a blank line appended to satisfy the parser`, async () => { + // skipped until internationalization is re-introduced + it.skip(`should create an internationalization config file with a blank line appended to satisfy the parser`, async () => { // Link to the error that happens when the blank line is not there: // https://github.com/elastic/kibana/pull/45044#issuecomment-530092627 const intlFile = `${generatedPath}/.i18nrc.json`; @@ -78,16 +79,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug }); }); - it(`'yarn test:server' should exit 0`, async () => { - await execa('yarn', ['test:mocha'], { - cwd: generatedPath, - env: { - DISABLE_JUNIT_REPORTER: '1', - }, - }); - }); - - it(`'yarn build' should exit 0`, async () => { + it.skip(`'yarn build' should exit 0`, async () => { await execa('yarn', ['build'], { cwd: generatedPath }); }); @@ -113,7 +105,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug '--migrations.skip=true', ], cwd: generatedPath, - wait: /ispec_plugin.+Status changed from uninitialized to green - Ready/, + wait: new RegExp('\\[ispecPlugin\\]\\[plugins\\] Setting up plugin'), }); await pr.stop('kibana'); }); @@ -123,7 +115,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug await execa('yarn', ['preinstall'], { cwd: generatedPath }); }); - it(`'yarn lint' should exit 0`, async () => { + it.skip(`'yarn lint' should exit 0`, async () => { await execa('yarn', ['lint'], { cwd: generatedPath }); }); diff --git a/packages/kbn-plugin-generator/sao_template/sao.js b/packages/kbn-plugin-generator/sao_template/sao.js index f7401cba84358..aed4b9a02838f 100755 --- a/packages/kbn-plugin-generator/sao_template/sao.js +++ b/packages/kbn-plugin-generator/sao_template/sao.js @@ -17,21 +17,19 @@ * under the License. */ -const { resolve, relative, dirname } = require('path'); +const { relative } = require('path'); const startCase = require('lodash.startcase'); const camelCase = require('lodash.camelcase'); const snakeCase = require('lodash.snakecase'); -const execa = require('execa'); const chalk = require('chalk'); +const execa = require('execa'); const pkg = require('../package.json'); const kibanaPkgPath = require.resolve('../../../package.json'); const kibanaPkg = require(kibanaPkgPath); // eslint-disable-line import/no-dynamic-require -const KBN_DIR = dirname(kibanaPkgPath); - -module.exports = function({ name }) { +module.exports = function({ name, targetPath, isKibanaPlugin }) { return { prompts: { description: { @@ -47,41 +45,38 @@ module.exports = function({ name }) { message: 'Should an app component be generated?', default: true, }, - generateTranslations: { - type: 'confirm', - message: 'Should translation files be generated?', - default: true, - }, - generateHack: { - type: 'confirm', - message: 'Should a hack component be generated?', - default: true, - }, generateApi: { type: 'confirm', message: 'Should a server API be generated?', default: true, }, + // generateTranslations: { + // type: 'confirm', + // message: 'Should translation files be generated?', + // default: true, + // }, generateScss: { type: 'confirm', message: 'Should SCSS be used?', when: answers => answers.generateApp, default: true, }, + generateEslint: { + type: 'confirm', + message: 'Would you like to use a custom eslint file?', + default: !isKibanaPlugin, + }, }, filters: { + 'public/**/index.scss': 'generateScss', 'public/**/*': 'generateApp', - 'translations/**/*': 'generateTranslations', - '.i18nrc.json': 'generateTranslations', - 'public/hack.js': 'generateHack', 'server/**/*': 'generateApi', - 'public/app.scss': 'generateScss', - '.kibana-plugin-helpers.json': 'generateScss', + // 'translations/**/*': 'generateTranslations', + // '.i18nrc.json': 'generateTranslations', + 'eslintrc.js': 'generateEslint', }, move: { - gitignore: '.gitignore', 'eslintrc.js': '.eslintrc.js', - 'package_template.json': 'package.json', }, data: answers => Object.assign( @@ -91,34 +86,36 @@ module.exports = function({ name }) { camelCase, snakeCase, name, + isKibanaPlugin, + kbnVersion: answers.kbnVersion, + upperCamelCaseName: name.charAt(0).toUpperCase() + camelCase(name).slice(1), + hasUi: !!answers.generateApp, + hasServer: !!answers.generateApi, + hasScss: !!answers.generateScss, + relRoot: isKibanaPlugin ? '../../../..' : '../../..', }, answers ), enforceNewFolder: true, installDependencies: false, - gitInit: true, + gitInit: !isKibanaPlugin, async post({ log }) { - await execa('yarn', ['kbn', 'bootstrap'], { - cwd: KBN_DIR, - stdio: 'inherit', - }); - - const dir = relative(process.cwd(), resolve(KBN_DIR, 'plugins', snakeCase(name))); + const dir = relative(process.cwd(), targetPath); + // Apply eslint to the generated plugin try { - await execa('yarn', ['lint', '--fix'], { - cwd: dir, - all: true, - }); + await execa('yarn', ['lint:es', `./${dir}/**/*.ts*`, '--no-ignore', '--fix']); } catch (error) { - throw new Error(`Failure when running prettier on the generated output: ${error.all}`); + console.error(error); + throw new Error( + `Failure when running prettier on the generated output: ${error.all || error}` + ); } log.success(chalk`🎉 -Your plugin has been created in {bold ${dir}}. Move into that directory to run it: +Your plugin has been created in {bold ${dir}}. - {bold cd "${dir}"} {bold yarn start} `); }, diff --git a/packages/kbn-plugin-generator/sao_template/sao.test.js b/packages/kbn-plugin-generator/sao_template/sao.test.js index 80149c008dad8..0dbdb7d3c097b 100755 --- a/packages/kbn-plugin-generator/sao_template/sao.test.js +++ b/packages/kbn-plugin-generator/sao_template/sao.test.js @@ -19,8 +19,6 @@ const sao = require('sao'); -const templatePkg = require('../package.json'); - const template = { fromPath: __dirname, configOptions: { @@ -32,121 +30,57 @@ function getFileContents(file) { return file.contents.toString(); } -function getConfig(file) { - const contents = getFileContents(file).replace(/\r?\n/gm, ''); - return contents.split('kibana.Plugin(')[1]; -} - describe('plugin generator sao integration', () => { test('skips files when answering no', async () => { const res = await sao.mockPrompt(template, { generateApp: false, - generateHack: false, generateApi: false, }); - expect(res.fileList).not.toContain('public/app.js'); - expect(res.fileList).not.toContain('public/__tests__/index.js'); - expect(res.fileList).not.toContain('public/hack.js'); - expect(res.fileList).not.toContain('server/routes/example.js'); - expect(res.fileList).not.toContain('server/__tests__/index.js'); - - const uiExports = getConfig(res.files['index.js']); - expect(uiExports).not.toContain('app:'); - expect(uiExports).not.toContain('hacks:'); - expect(uiExports).not.toContain('init(server, options)'); - expect(uiExports).not.toContain('registerFeature('); + expect(res.fileList).toContain('common/index.ts'); + expect(res.fileList).not.toContain('public/index.ts'); + expect(res.fileList).not.toContain('server/index.ts'); }); it('includes app when answering yes', async () => { const res = await sao.mockPrompt(template, { generateApp: true, - generateHack: false, - generateApi: false, - }); - - // check output files - expect(res.fileList).toContain('public/app.js'); - expect(res.fileList).toContain('public/__tests__/index.js'); - expect(res.fileList).not.toContain('public/hack.js'); - expect(res.fileList).not.toContain('server/routes/example.js'); - expect(res.fileList).not.toContain('server/__tests__/index.js'); - - const uiExports = getConfig(res.files['index.js']); - expect(uiExports).toContain('app:'); - expect(uiExports).toContain('init(server, options)'); - expect(uiExports).toContain('registerFeature('); - expect(uiExports).not.toContain('hacks:'); - }); - - it('includes hack when answering yes', async () => { - const res = await sao.mockPrompt(template, { - generateApp: true, - generateHack: true, generateApi: false, }); // check output files - expect(res.fileList).toContain('public/app.js'); - expect(res.fileList).toContain('public/__tests__/index.js'); - expect(res.fileList).toContain('public/hack.js'); - expect(res.fileList).not.toContain('server/routes/example.js'); - expect(res.fileList).not.toContain('server/__tests__/index.js'); - - const uiExports = getConfig(res.files['index.js']); - expect(uiExports).toContain('app:'); - expect(uiExports).toContain('hacks:'); - expect(uiExports).toContain('init(server, options)'); - expect(uiExports).toContain('registerFeature('); + expect(res.fileList).toContain('common/index.ts'); + expect(res.fileList).toContain('public/index.ts'); + expect(res.fileList).toContain('public/plugin.ts'); + expect(res.fileList).toContain('public/types.ts'); + expect(res.fileList).toContain('public/components/app.tsx'); + expect(res.fileList).not.toContain('server/index.ts'); }); it('includes server api when answering yes', async () => { const res = await sao.mockPrompt(template, { generateApp: true, - generateHack: true, generateApi: true, }); // check output files - expect(res.fileList).toContain('public/app.js'); - expect(res.fileList).toContain('public/__tests__/index.js'); - expect(res.fileList).toContain('public/hack.js'); - expect(res.fileList).toContain('server/routes/example.js'); - expect(res.fileList).toContain('server/__tests__/index.js'); - - const uiExports = getConfig(res.files['index.js']); - expect(uiExports).toContain('app:'); - expect(uiExports).toContain('hacks:'); - expect(uiExports).toContain('init(server, options)'); - expect(uiExports).toContain('registerFeature('); - }); - - it('plugin config has correct name and main path', async () => { - const res = await sao.mockPrompt(template, { - generateApp: true, - generateHack: true, - generateApi: true, - }); - - const indexContents = getFileContents(res.files['index.js']); - const nameLine = indexContents.match('name: (.*)')[1]; - const mainLine = indexContents.match('main: (.*)')[1]; - - expect(nameLine).toContain('some_fancy_plugin'); - expect(mainLine).toContain('plugins/some_fancy_plugin/app'); + expect(res.fileList).toContain('public/plugin.ts'); + expect(res.fileList).toContain('server/plugin.ts'); + expect(res.fileList).toContain('server/index.ts'); + expect(res.fileList).toContain('server/types.ts'); + expect(res.fileList).toContain('server/routes/index.ts'); }); - it('plugin package has correct name', async () => { + it('plugin package has correct title', async () => { const res = await sao.mockPrompt(template, { generateApp: true, - generateHack: true, generateApi: true, }); - const packageContents = getFileContents(res.files['package.json']); - const pkg = JSON.parse(packageContents); + const contents = getFileContents(res.files['common/index.ts']); + const controllerLine = contents.match("PLUGIN_NAME = '(.*)'")[1]; - expect(pkg.name).toBe('some_fancy_plugin'); + expect(controllerLine).toContain('Some fancy plugin'); }); it('package has version "kibana" with master', async () => { @@ -154,10 +88,10 @@ describe('plugin generator sao integration', () => { kbnVersion: 'master', }); - const packageContents = getFileContents(res.files['package.json']); + const packageContents = getFileContents(res.files['kibana.json']); const pkg = JSON.parse(packageContents); - expect(pkg.kibana.version).toBe('kibana'); + expect(pkg.version).toBe('master'); }); it('package has correct version', async () => { @@ -165,39 +99,26 @@ describe('plugin generator sao integration', () => { kbnVersion: 'v6.0.0', }); - const packageContents = getFileContents(res.files['package.json']); - const pkg = JSON.parse(packageContents); - - expect(pkg.kibana.version).toBe('v6.0.0'); - }); - - it('package has correct templateVersion', async () => { - const res = await sao.mockPrompt(template, { - kbnVersion: 'master', - }); - - const packageContents = getFileContents(res.files['package.json']); + const packageContents = getFileContents(res.files['kibana.json']); const pkg = JSON.parse(packageContents); - expect(pkg.kibana.templateVersion).toBe(templatePkg.version); + expect(pkg.version).toBe('v6.0.0'); }); it('sample app has correct values', async () => { const res = await sao.mockPrompt(template, { generateApp: true, - generateHack: true, generateApi: true, }); - const contents = getFileContents(res.files['public/app.js']); - const controllerLine = contents.match('setRootController(.*)')[1]; + const contents = getFileContents(res.files['common/index.ts']); + const controllerLine = contents.match("PLUGIN_ID = '(.*)'")[1]; expect(controllerLine).toContain('someFancyPlugin'); }); it('includes dotfiles', async () => { const res = await sao.mockPrompt(template); - expect(res.files['.gitignore']).toBeTruthy(); expect(res.files['.eslintrc.js']).toBeTruthy(); }); }); diff --git a/packages/kbn-plugin-generator/sao_template/template/.i18nrc.json b/packages/kbn-plugin-generator/sao_template/template/.i18nrc.json deleted file mode 100644 index 1a8aea8853876..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/.i18nrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "paths": { - "<%= camelCase(name) %>": "./" - }, - "translations": [ - "translations/zh-CN.json" - ] -} - diff --git a/packages/kbn-plugin-generator/sao_template/template/.kibana-plugin-helpers.json b/packages/kbn-plugin-generator/sao_template/template/.kibana-plugin-helpers.json deleted file mode 100644 index 383368c7f8ce1..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/.kibana-plugin-helpers.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "styleSheetToCompile": "public/app.scss" -} diff --git a/packages/kbn-plugin-generator/sao_template/template/README.md b/packages/kbn-plugin-generator/sao_template/template/README.md index f475c7110293a..008d500abbbf5 100755 --- a/packages/kbn-plugin-generator/sao_template/template/README.md +++ b/packages/kbn-plugin-generator/sao_template/template/README.md @@ -6,6 +6,6 @@ --- -## development +## Development See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/packages/kbn-plugin-generator/sao_template/template/common/index.ts b/packages/kbn-plugin-generator/sao_template/template/common/index.ts new file mode 100644 index 0000000000000..90ffcb70045aa --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/common/index.ts @@ -0,0 +1,2 @@ +export const PLUGIN_ID = '<%= camelCase(name) %>'; +export const PLUGIN_NAME = '<%= name %>'; diff --git a/packages/kbn-plugin-generator/sao_template/template/eslintrc.js b/packages/kbn-plugin-generator/sao_template/template/eslintrc.js old mode 100755 new mode 100644 index e1dfadc212b7e..b68d42e32e047 --- a/packages/kbn-plugin-generator/sao_template/template/eslintrc.js +++ b/packages/kbn-plugin-generator/sao_template/template/eslintrc.js @@ -1,24 +1,9 @@ -module.exports = { - root: true, +module.exports = { + root: true, extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'], - settings: { - 'import/resolver': { - '@kbn/eslint-import-resolver-kibana': { - rootPackageName: '<%= snakeCase(name) %>', - }, - }, - }, - overrides: [ - { - files: ['**/public/**/*'], - settings: { - 'import/resolver': { - '@kbn/eslint-import-resolver-kibana': { - forceNode: false, - rootPackageName: '<%= snakeCase(name) %>', - }, - }, - }, - }, - ] -}; + <%_ if (!isKibanaPlugin) { -%> + rules: { + "@kbn/eslint/require-license-header": "off" + } + <%_ } -%> +}; \ No newline at end of file diff --git a/packages/kbn-plugin-generator/sao_template/template/gitignore b/packages/kbn-plugin-generator/sao_template/template/gitignore deleted file mode 100755 index db28fed19376d..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/gitignore +++ /dev/null @@ -1,6 +0,0 @@ -npm-debug.log* -node_modules -/build/ -<%_ if (generateScss) { -%> -/public/app.css -<%_ } -%> diff --git a/packages/kbn-plugin-generator/sao_template/template/index.js b/packages/kbn-plugin-generator/sao_template/template/index.js deleted file mode 100755 index 4bc3347ae6019..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/index.js +++ /dev/null @@ -1,89 +0,0 @@ -<% if (generateScss) { -%> -import { resolve } from 'path'; -import { existsSync } from 'fs'; - -<% } -%> - -<% if (generateApp) { -%> -import { i18n } from '@kbn/i18n'; -<% } -%> - -<% if (generateApi) { -%> -import exampleRoute from './server/routes/example'; - -<% } -%> -export default function (kibana) { - return new kibana.Plugin({ - require: ['elasticsearch'], - name: '<%= snakeCase(name) %>', - uiExports: { - <%_ if (generateApp) { -%> - app: { - title: '<%= startCase(name) %>', - description: '<%= description %>', - main: 'plugins/<%= snakeCase(name) %>/app', - }, - <%_ } -%> - <%_ if (generateHack) { -%> - hacks: [ - 'plugins/<%= snakeCase(name) %>/hack' - ], - <%_ } -%> - <%_ if (generateScss) { -%> - styleSheetPaths: [resolve(__dirname, 'public/app.scss'), resolve(__dirname, 'public/app.css')].find(p => existsSync(p)), - <%_ } -%> - }, - - config(Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - <%_ if (generateApi || generateApp) { -%> - - // eslint-disable-next-line no-unused-vars - init(server, options) { - <%_ if (generateApp) { -%> - const xpackMainPlugin = server.plugins.xpack_main; - if (xpackMainPlugin) { - const featureId = '<%= snakeCase(name) %>'; - - xpackMainPlugin.registerFeature({ - id: featureId, - name: i18n.translate('<%= camelCase(name) %>.featureRegistry.featureName', { - defaultMessage: '<%= name %>', - }), - navLinkId: featureId, - icon: 'questionInCircle', - app: [featureId, 'kibana'], - catalogue: [], - privileges: { - all: { - api: [], - savedObject: { - all: [], - read: [], - }, - ui: ['show'], - }, - read: { - api: [], - savedObject: { - all: [], - read: [], - }, - ui: ['show'], - }, - }, - }); - } - <%_ } -%> - - <%_ if (generateApi) { -%> - // Add server routes and initialize the plugin here - exampleRoute(server); - <%_ } -%> - } - <%_ } -%> - }); -} diff --git a/packages/kbn-plugin-generator/sao_template/template/kibana.json b/packages/kbn-plugin-generator/sao_template/template/kibana.json new file mode 100644 index 0000000000000..f8bb07040abeb --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "<%= camelCase(name) %>", + "version": "<%= kbnVersion %>", + "server": <%= hasServer %>, + "ui": <%= hasUi %>, + "requiredPlugins": ["navigation"], + "optionalPlugins": [] +} diff --git a/packages/kbn-plugin-generator/sao_template/template/package_template.json b/packages/kbn-plugin-generator/sao_template/template/package_template.json deleted file mode 100644 index 94de03f7972e5..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/package_template.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "<%= snakeCase(name) %>", - "version": "0.0.0", - "description": "<%= description %>", - "main": "index.js", - "kibana": { - "version": "<%= (kbnVersion === 'master') ? 'kibana' : kbnVersion %>", - "templateVersion": "<%= templateVersion %>" - }, - "scripts": { - "preinstall": "node ../../preinstall_check", - "kbn": "node ../../scripts/kbn", - "es": "node ../../scripts/es", - "lint": "eslint .", - "start": "plugin-helpers start", - "test:mocha": "plugin-helpers test:mocha", - "test:karma": "plugin-helpers test:karma", - "build": "plugin-helpers build" - }, - <%_ if (generateTranslations) { _%> - "dependencies": { - "@kbn/i18n": "link:../../packages/kbn-i18n" - }, - <%_ - } _%> - "devDependencies": { - "@elastic/eslint-config-kibana": "link:../../packages/eslint-config-kibana", - "@elastic/eslint-import-resolver-kibana": "link:../../packages/kbn-eslint-import-resolver-kibana", - "@kbn/expect": "link:../../packages/kbn-expect", - "@kbn/plugin-helpers": "link:../../packages/kbn-plugin-helpers", - "babel-eslint": "^10.0.1", - "eslint": "^5.16.0", - "eslint-plugin-babel": "^5.3.0", - "eslint-plugin-import": "^2.16.0", - "eslint-plugin-jest": "^22.4.1", - "eslint-plugin-jsx-a11y": "^6.2.1", - "eslint-plugin-mocha": "^5.3.0", - "eslint-plugin-no-unsanitized": "^3.0.2", - "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-react": "^7.12.4" - } -} \ No newline at end of file diff --git a/packages/kbn-plugin-generator/sao_template/template/public/__tests__/index.js b/packages/kbn-plugin-generator/sao_template/template/public/__tests__/index.js deleted file mode 100755 index 9320bd7b028a8..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/public/__tests__/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import expect from '@kbn/expect'; - -describe('suite', () => { - it('is a test', () => { - expect(true).to.equal(true); - }); -}); diff --git a/packages/kbn-plugin-generator/sao_template/template/public/app.js b/packages/kbn-plugin-generator/sao_template/template/public/app.js deleted file mode 100755 index 37a7c37e916a0..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/public/app.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { uiModules } from 'ui/modules'; -import chrome from 'ui/chrome'; -import { render, unmountComponentAtNode } from 'react-dom'; -<%_ if (generateTranslations) { _%> -import { I18nProvider } from '@kbn/i18n/react'; -<%_ } _%> - -import { Main } from './components/main'; - -const app = uiModules.get('apps/<%= camelCase(name) %>'); - -app.config($locationProvider => { - $locationProvider.html5Mode({ - enabled: false, - requireBase: false, - rewriteLinks: false, - }); -}); -app.config(stateManagementConfigProvider => - stateManagementConfigProvider.disable() -); - -function RootController($scope, $element, $http) { - const domNode = $element[0]; - - // render react to DOM - <%_ if (generateTranslations) { _%> - render( - -
- , - domNode - ); - <%_ } else { _%> - render(
, domNode); - <%_ } _%> - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); -} - -chrome.setRootController('<%= camelCase(name) %>', RootController); diff --git a/packages/kbn-plugin-generator/sao_template/template/public/application.tsx b/packages/kbn-plugin-generator/sao_template/template/public/application.tsx new file mode 100644 index 0000000000000..8106a18a784e7 --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/public/application.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { AppMountParameters, CoreStart } from '<%= relRoot %>/src/core/public'; +import { AppPluginStartDependencies } from './types'; +import { <%= upperCamelCaseName %>App } from './components/app'; + + +export const renderApp = ( + { notifications, http }: CoreStart, + { navigation }: AppPluginStartDependencies, + { appBasePath, element }: AppMountParameters + ) => { + ReactDOM.render( + <<%= upperCamelCaseName %>App + basename={appBasePath} + notifications={notifications} + http={http} + navigation={navigation} + />, + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); + }; + \ No newline at end of file diff --git a/packages/kbn-plugin-generator/sao_template/template/public/components/app.tsx b/packages/kbn-plugin-generator/sao_template/template/public/components/app.tsx new file mode 100644 index 0000000000000..7b259a9c5b99d --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/public/components/app.tsx @@ -0,0 +1,129 @@ +/* + * 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, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import { BrowserRouter as Router } from 'react-router-dom'; + +import { + EuiButton, + EuiHorizontalRule, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageHeader, + EuiTitle, + EuiText, +} from '@elastic/eui'; + +import { CoreStart } from '<%= relRoot %>/../src/core/public'; +import { NavigationPublicPluginStart } from '<%= relRoot %>/../src/plugins/navigation/public'; + +import { PLUGIN_ID, PLUGIN_NAME } from '../../common'; + +interface <%= upperCamelCaseName %>AppDeps { + basename: string; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + navigation: NavigationPublicPluginStart; +} + +export const <%= upperCamelCaseName %>App = ({ basename, notifications, http, navigation }: <%= upperCamelCaseName %>AppDeps) => { + // Use React hooks to manage state. + const [timestamp, setTimestamp] = useState(); + + const onClickHandler = () => { +<%_ if (generateApi) { -%> + // Use the core http service to make a response to the server API. + http.get('/api/<%= snakeCase(name) %>/example').then(res => { + setTimestamp(res.time); + // Use the core notifications service to display a success message. + notifications.toasts.addSuccess(i18n.translate('<%= camelCase(name) %>.dataUpdated', { + defaultMessage: 'Data updated', + })); + }); +<%_ } else { -%> + setTimestamp(new Date().toISOString()); + notifications.toasts.addSuccess(PLUGIN_NAME); +<%_ } -%> + }; + + // Render the application DOM. + // Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract. + return ( + + + <> + + + + + +

+ +

+
+
+ + + +

+ +

+
+
+ + +

+ +

+ +

+ +

+ + + +
+
+
+
+
+ +
+
+ ); +}; diff --git a/packages/kbn-plugin-generator/sao_template/template/public/components/main/index.js b/packages/kbn-plugin-generator/sao_template/template/public/components/main/index.js deleted file mode 100644 index 68710baa1bee8..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/public/components/main/index.js +++ /dev/null @@ -1 +0,0 @@ -export { Main } from './main'; diff --git a/packages/kbn-plugin-generator/sao_template/template/public/components/main/main.js b/packages/kbn-plugin-generator/sao_template/template/public/components/main/main.js deleted file mode 100644 index 59fd667c709aa..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/public/components/main/main.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { - EuiPage, - EuiPageHeader, - EuiTitle, - EuiPageBody, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentBody, - EuiText -} from '@elastic/eui'; -<%_ if (generateTranslations) { _%> -import { FormattedMessage } from '@kbn/i18n/react'; -<%_ } _%> - -export class Main extends React.Component { - constructor(props) { - super(props); - this.state = {}; - } - - componentDidMount() { - /* - FOR EXAMPLE PURPOSES ONLY. There are much better ways to - manage state and update your UI than this. - */ - const { httpClient } = this.props; - httpClient.get('../api/<%= name %>/example').then((resp) => { - this.setState({ time: resp.data.time }); - }); - } - render() { - const { title } = this.props; - return ( - - - - -

- <%_ if (generateTranslations) { _%> - - <%_ } else { _%> - {title} Hello World! - <%_ } _%> -

-
-
- - - -

- <%_ if (generateTranslations) { _%> - - <%_ } else { _%> - Congratulations - <%_ } _%> -

-
-
- - -

- <%_ if (generateTranslations) { _%> - - <%_ } else { _%> - You have successfully created your first Kibana Plugin! - <%_ } _%> -

-

- <%_ if (generateTranslations) { _%> - - <%_ } else { _%> - The server time (via API call) is {this.state.time || 'NO API CALL YET'} - <%_ } _%> -

-
-
-
-
-
- ); - } -} diff --git a/packages/kbn-plugin-generator/sao_template/template/public/hack.js b/packages/kbn-plugin-generator/sao_template/template/public/hack.js deleted file mode 100755 index 775526c8e44a3..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/public/hack.js +++ /dev/null @@ -1,7 +0,0 @@ -import $ from 'jquery'; - -$(document.body).on('keypress', function (event) { - if (event.which === 58) { - alert('boo!'); - } -}); diff --git a/packages/kbn-plugin-generator/sao_template/template/public/app.scss b/packages/kbn-plugin-generator/sao_template/template/public/index.scss similarity index 100% rename from packages/kbn-plugin-generator/sao_template/template/public/app.scss rename to packages/kbn-plugin-generator/sao_template/template/public/index.scss diff --git a/packages/kbn-plugin-generator/sao_template/template/public/index.ts b/packages/kbn-plugin-generator/sao_template/template/public/index.ts new file mode 100644 index 0000000000000..2999dc7264ddb --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/public/index.ts @@ -0,0 +1,16 @@ +<%_ if (hasScss) { -%> +import './index.scss'; +<%_ } -%> + +import { <%= upperCamelCaseName %>Plugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. +export function plugin() { + return new <%= upperCamelCaseName %>Plugin(); +} +export { + <%= upperCamelCaseName %>PluginSetup, + <%= upperCamelCaseName %>PluginStart, +} from './types'; + diff --git a/packages/kbn-plugin-generator/sao_template/template/public/plugin.ts b/packages/kbn-plugin-generator/sao_template/template/public/plugin.ts new file mode 100644 index 0000000000000..76f7f1a6f9908 --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/public/plugin.ts @@ -0,0 +1,42 @@ +import { i18n } from '@kbn/i18n'; +import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '<%= relRoot %>/src/core/public'; +import { <%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart, AppPluginStartDependencies } from './types'; +import { PLUGIN_NAME } from '../common'; + +export class <%= upperCamelCaseName %>Plugin + implements Plugin<<%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart> { + + public setup(core: CoreSetup): <%= upperCamelCaseName %>PluginSetup { + // Register an application into the side navigation menu + core.application.register({ + id: '<%= camelCase(name) %>', + title: PLUGIN_NAME, + async mount(params: AppMountParameters) { + // Load application bundle + const { renderApp } = await import('./application'); + // Get start services as specified in kibana.json + const [coreStart, depsStart] = await core.getStartServices(); + // Render the application + return renderApp(coreStart, depsStart as AppPluginStartDependencies, params); + }, + }); + + // Return methods that should be available to other plugins + return { + getGreeting() { + return i18n.translate('<%= camelCase(name) %>.greetingText', { + defaultMessage: 'Hello from {name}!', + values: { + name: PLUGIN_NAME, + }, + }); + }, + }; + } + + public start(core: CoreStart): <%= upperCamelCaseName %>PluginStart { + return {}; + } + + public stop() {} +} diff --git a/packages/kbn-plugin-generator/sao_template/template/public/types.ts b/packages/kbn-plugin-generator/sao_template/template/public/types.ts new file mode 100644 index 0000000000000..2ebb0c0d1257f --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/public/types.ts @@ -0,0 +1,11 @@ +import { NavigationPublicPluginStart } from '<%= relRoot %>/src/plugins/navigation/public'; + +export interface <%= upperCamelCaseName %>PluginSetup { + getGreeting: () => string; +} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface <%= upperCamelCaseName %>PluginStart {} + +export interface AppPluginStartDependencies { + navigation: NavigationPublicPluginStart +}; diff --git a/packages/kbn-plugin-generator/sao_template/template/server/__tests__/index.js b/packages/kbn-plugin-generator/sao_template/template/server/__tests__/index.js deleted file mode 100755 index 9320bd7b028a8..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/server/__tests__/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import expect from '@kbn/expect'; - -describe('suite', () => { - it('is a test', () => { - expect(true).to.equal(true); - }); -}); diff --git a/packages/kbn-plugin-generator/sao_template/template/server/index.ts b/packages/kbn-plugin-generator/sao_template/template/server/index.ts new file mode 100644 index 0000000000000..816b8faec2a45 --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/server/index.ts @@ -0,0 +1,15 @@ +import { PluginInitializerContext } from '<%= relRoot %>/src/core/server'; +import { <%= upperCamelCaseName %>Plugin } from './plugin'; + + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. + + export function plugin(initializerContext: PluginInitializerContext) { + return new <%= upperCamelCaseName %>Plugin(initializerContext); +} + +export { + <%= upperCamelCaseName %>PluginSetup, + <%= upperCamelCaseName %>PluginStart, +} from './types'; diff --git a/packages/kbn-plugin-generator/sao_template/template/server/plugin.ts b/packages/kbn-plugin-generator/sao_template/template/server/plugin.ts new file mode 100644 index 0000000000000..d6a343209e39e --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/server/plugin.ts @@ -0,0 +1,30 @@ +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '<%= relRoot %>/src/core/server'; + +import { <%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart } from './types'; +import { defineRoutes } from './routes'; + +export class <%= upperCamelCaseName %>Plugin + implements Plugin<<%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart> { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('<%= name %>: Setup'); + const router = core.http.createRouter(); + + // Register server side APIs + defineRoutes(router); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('<%= name %>: Started'); + return {}; + } + + public stop() {} +} diff --git a/packages/kbn-plugin-generator/sao_template/template/server/routes/example.js b/packages/kbn-plugin-generator/sao_template/template/server/routes/example.js deleted file mode 100755 index 5a612645f48fc..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/server/routes/example.js +++ /dev/null @@ -1,11 +0,0 @@ -export default function (server) { - - server.route({ - path: '/api/<%= name %>/example', - method: 'GET', - handler() { - return { time: (new Date()).toISOString() }; - } - }); - -} diff --git a/packages/kbn-plugin-generator/sao_template/template/server/routes/index.ts b/packages/kbn-plugin-generator/sao_template/template/server/routes/index.ts new file mode 100644 index 0000000000000..d8bb00f0dea6c --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/server/routes/index.ts @@ -0,0 +1,17 @@ +import { IRouter } from '<%= relRoot %>/../src/core/server'; + +export function defineRoutes(router: IRouter) { + router.get( + { + path: '/api/<%= snakeCase(name) %>/example', + validate: false, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new Date().toISOString(), + }, + }); + } + ); +} diff --git a/packages/kbn-plugin-generator/sao_template/template/server/types.ts b/packages/kbn-plugin-generator/sao_template/template/server/types.ts new file mode 100644 index 0000000000000..adbc5e93f03c5 --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/server/types.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface <%= upperCamelCaseName %>PluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface <%= upperCamelCaseName %>PluginStart {} diff --git a/packages/kbn-plugin-generator/sao_template/template/translations/zh-CN.json b/packages/kbn-plugin-generator/sao_template/template/translations/zh-CN.json deleted file mode 100644 index 3447511c6739a..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/translations/zh-CN.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "formats": { - "number": { - "currency": { - "style": "currency" - }, - "percent": { - "style": "percent" - } - }, - "date": { - "short": { - "month": "numeric", - "day": "numeric", - "year": "2-digit" - }, - "medium": { - "month": "short", - "day": "numeric", - "year": "numeric" - }, - "long": { - "month": "long", - "day": "numeric", - "year": "numeric" - }, - "full": { - "weekday": "long", - "month": "long", - "day": "numeric", - "year": "numeric" - } - }, - "time": { - "short": { - "hour": "numeric", - "minute": "numeric" - }, - "medium": { - "hour": "numeric", - "minute": "numeric", - "second": "numeric" - }, - "long": { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short" - }, - "full": { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short" - } - }, - "relative": { - "years": { - "units": "year" - }, - "months": { - "units": "month" - }, - "days": { - "units": "day" - }, - "hours": { - "units": "hour" - }, - "minutes": { - "units": "minute" - }, - "seconds": { - "units": "second" - } - } - }, - "messages": { - "<%= camelCase(name) %>.congratulationsText": "您已经成功创建第一个 Kibana 插件。", - "<%= camelCase(name) %>.congratulationsTitle": "恭喜!", - "<%= camelCase(name) %>.helloWorldText": "{title} 您好,世界!", - "<%= camelCase(name) %>.serverTimeText": "服务器时间(通过 API 调用)为 {time}" - } -} diff --git a/packages/kbn-plugin-generator/tsconfig.json b/packages/kbn-plugin-generator/tsconfig.json new file mode 100644 index 0000000000000..fe0f7112f1fa9 --- /dev/null +++ b/packages/kbn-plugin-generator/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["**/*", "index.js.d.ts"], + "exclude": ["sao_template/template/*"] +} diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index ca2f6789bebee..ba1988b857385 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -932,7 +932,7 @@ export type RecursiveReadonly = T extends (...args: any[]) => any ? T : T ext }> : T; // @public (undocumented) -export interface SavedObject { +export interface SavedObject { attributes: T; // (undocumented) error?: { @@ -975,13 +975,13 @@ export interface SavedObjectsBaseOptions { } // @public (undocumented) -export interface SavedObjectsBatchResponse { +export interface SavedObjectsBatchResponse { // (undocumented) savedObjects: Array>; } // @public (undocumented) -export interface SavedObjectsBulkCreateObject extends SavedObjectsCreateOptions { +export interface SavedObjectsBulkCreateObject extends SavedObjectsCreateOptions { // (undocumented) attributes: T; // (undocumented) @@ -994,7 +994,7 @@ export interface SavedObjectsBulkCreateOptions { } // @public (undocumented) -export interface SavedObjectsBulkUpdateObject { +export interface SavedObjectsBulkUpdateObject { // (undocumented) attributes: T; // (undocumented) @@ -1017,17 +1017,17 @@ export interface SavedObjectsBulkUpdateOptions { export class SavedObjectsClient { // @internal constructor(http: HttpSetup); - bulkCreate: (objects?: SavedObjectsBulkCreateObject[], options?: SavedObjectsBulkCreateOptions) => Promise>; + bulkCreate: (objects?: SavedObjectsBulkCreateObject[], options?: SavedObjectsBulkCreateOptions) => Promise>; bulkGet: (objects?: { id: string; type: string; - }[]) => Promise>; - bulkUpdate(objects?: SavedObjectsBulkUpdateObject[]): Promise>; - create: (type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise>; + }[]) => Promise>; + bulkUpdate(objects?: SavedObjectsBulkUpdateObject[]): Promise>; + create: (type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise>; delete: (type: string, id: string) => Promise<{}>; - find: (options: Pick) => Promise>; - get: (type: string, id: string) => Promise>; - update(type: string, id: string, attributes: T, { version, migrationVersion, references }?: SavedObjectsUpdateOptions): Promise>; + find: (options: Pick) => Promise>; + get: (type: string, id: string) => Promise>; + update(type: string, id: string, attributes: T, { version, migrationVersion, references }?: SavedObjectsUpdateOptions): Promise>; } // @public @@ -1069,7 +1069,7 @@ export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions { } // @public -export interface SavedObjectsFindResponsePublic extends SavedObjectsBatchResponse { +export interface SavedObjectsFindResponsePublic extends SavedObjectsBatchResponse { // (undocumented) page: number; // (undocumented) @@ -1176,7 +1176,7 @@ export interface SavedObjectsUpdateOptions { } // @public -export class SimpleSavedObject { +export class SimpleSavedObject { constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion }: SavedObject); // (undocumented) attributes: T; diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index ccb23793a8534..afc77806afb91 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -22,7 +22,6 @@ import { resolve as resolveUrl } from 'url'; import { SavedObject, - SavedObjectAttributes, SavedObjectReference, SavedObjectsClientContract as SavedObjectsApi, SavedObjectsFindOptions as SavedObjectFindOptionsServer, @@ -61,9 +60,7 @@ export interface SavedObjectsCreateOptions { * * @public */ -export interface SavedObjectsBulkCreateObject< - T extends SavedObjectAttributes = SavedObjectAttributes -> extends SavedObjectsCreateOptions { +export interface SavedObjectsBulkCreateObject extends SavedObjectsCreateOptions { type: string; attributes: T; } @@ -75,9 +72,7 @@ export interface SavedObjectsBulkCreateOptions { } /** @public */ -export interface SavedObjectsBulkUpdateObject< - T extends SavedObjectAttributes = SavedObjectAttributes -> { +export interface SavedObjectsBulkUpdateObject { type: string; id: string; attributes: T; @@ -99,9 +94,7 @@ export interface SavedObjectsUpdateOptions { } /** @public */ -export interface SavedObjectsBatchResponse< - T extends SavedObjectAttributes = SavedObjectAttributes -> { +export interface SavedObjectsBatchResponse { savedObjects: Array>; } @@ -113,9 +106,7 @@ export interface SavedObjectsBatchResponse< * * @public */ -export interface SavedObjectsFindResponsePublic< - T extends SavedObjectAttributes = SavedObjectAttributes -> extends SavedObjectsBatchResponse { +export interface SavedObjectsFindResponsePublic extends SavedObjectsBatchResponse { total: number; perPage: number; page: number; @@ -124,7 +115,7 @@ export interface SavedObjectsFindResponsePublic< interface BatchQueueEntry { type: string; id: string; - resolve: (value: SimpleSavedObject | SavedObject) => void; + resolve: (value: SimpleSavedObject | SavedObject) => void; reject: (reason?: any) => void; } @@ -207,7 +198,7 @@ export class SavedObjectsClient { * @param options * @returns */ - public create = ( + public create = ( type: string, attributes: T, options: SavedObjectsCreateOptions = {} @@ -300,7 +291,7 @@ export class SavedObjectsClient { * @property {object} [options.hasReference] - { type, id } * @returns A find result with objects matching the specified search. */ - public find = ( + public find = ( options: SavedObjectsFindOptions ): Promise> => { const path = this.getPath(['_find']); @@ -348,10 +339,7 @@ export class SavedObjectsClient { * @param {string} id * @returns The saved object for the given type and id. */ - public get = ( - type: string, - id: string - ): Promise> => { + public get = (type: string, id: string): Promise> => { if (!type || !id) { return Promise.reject(new Error('requires type and id')); } @@ -402,7 +390,7 @@ export class SavedObjectsClient { * @prop {object} options.migrationVersion - The optional migrationVersion of this document * @returns */ - public update( + public update( type: string, id: string, attributes: T, @@ -434,7 +422,7 @@ export class SavedObjectsClient { * @param {array} objects - [{ type, id, attributes, options: { version, references } }] * @returns The result of the update operation containing both failed and updated saved objects. */ - public bulkUpdate(objects: SavedObjectsBulkUpdateObject[] = []) { + public bulkUpdate(objects: SavedObjectsBulkUpdateObject[] = []) { const path = this.getPath(['_bulk_update']); return this.savedObjectsFetch(path, { @@ -449,9 +437,7 @@ export class SavedObjectsClient { }); } - private createSavedObject( - options: SavedObject - ): SimpleSavedObject { + private createSavedObject(options: SavedObject): SimpleSavedObject { return new SimpleSavedObject(this, options); } diff --git a/src/core/public/saved_objects/simple_saved_object.ts b/src/core/public/saved_objects/simple_saved_object.ts index 8e464680bcf17..d3ba506b865a4 100644 --- a/src/core/public/saved_objects/simple_saved_object.ts +++ b/src/core/public/saved_objects/simple_saved_object.ts @@ -18,7 +18,7 @@ */ import { get, has, set } from 'lodash'; -import { SavedObject as SavedObjectType, SavedObjectAttributes } from '../../server'; +import { SavedObject as SavedObjectType } from '../../server'; import { SavedObjectsClientContract } from './saved_objects_client'; /** @@ -30,7 +30,7 @@ import { SavedObjectsClientContract } from './saved_objects_client'; * * @public */ -export class SimpleSavedObject { +export class SimpleSavedObject { public attributes: T; // We want to use the same interface this class had in JS public _version?: SavedObjectType['version']; @@ -46,7 +46,7 @@ export class SimpleSavedObject { ) { this.id = id; this.type = type; - this.attributes = attributes || {}; + this.attributes = attributes || ({} as T); this.references = references || []; this._version = version; this.migrationVersion = migrationVersion; diff --git a/src/core/server/saved_objects/import/collect_saved_objects.ts b/src/core/server/saved_objects/import/collect_saved_objects.ts index 65ffd4d9a1d57..1a8ede41d0b2c 100644 --- a/src/core/server/saved_objects/import/collect_saved_objects.ts +++ b/src/core/server/saved_objects/import/collect_saved_objects.ts @@ -42,10 +42,10 @@ export async function collectSavedObjects({ supportedTypes, }: CollectSavedObjectsOptions) { const errors: SavedObjectsImportError[] = []; - const collectedObjects: SavedObject[] = await createPromiseFromStreams([ + const collectedObjects: Array> = await createPromiseFromStreams([ readStream, createLimitStream(objectLimit), - createFilterStream(obj => { + createFilterStream>(obj => { if (supportedTypes.includes(obj.type)) { return true; } diff --git a/src/core/server/saved_objects/import/extract_errors.ts b/src/core/server/saved_objects/import/extract_errors.ts index 725e935f6e21d..5728ce8b7b59f 100644 --- a/src/core/server/saved_objects/import/extract_errors.ts +++ b/src/core/server/saved_objects/import/extract_errors.ts @@ -20,11 +20,12 @@ import { SavedObject } from '../types'; import { SavedObjectsImportError } from './types'; export function extractErrors( - savedObjectResults: SavedObject[], - savedObjectsToImport: SavedObject[] + // TODO: define saved object type + savedObjectResults: Array>, + savedObjectsToImport: Array> ) { const errors: SavedObjectsImportError[] = []; - const originalSavedObjectsMap = new Map(); + const originalSavedObjectsMap = new Map>(); for (const savedObject of savedObjectsToImport) { originalSavedObjectsMap.set(`${savedObject.type}:${savedObject.id}`, savedObject); } diff --git a/src/core/server/saved_objects/import/validate_references.ts b/src/core/server/saved_objects/import/validate_references.ts index 4d9ee59f9df15..f0c033c1d00b4 100644 --- a/src/core/server/saved_objects/import/validate_references.ts +++ b/src/core/server/saved_objects/import/validate_references.ts @@ -77,7 +77,7 @@ export async function getNonExistingReferenceAsKeys( } export async function validateReferences( - savedObjects: SavedObject[], + savedObjects: Array>, savedObjectsClient: SavedObjectsClientContract, namespace?: string ) { diff --git a/src/core/server/saved_objects/management/management.ts b/src/core/server/saved_objects/management/management.ts index 7b5274da91fc8..b7dce2c087c5f 100644 --- a/src/core/server/saved_objects/management/management.ts +++ b/src/core/server/saved_objects/management/management.ts @@ -23,9 +23,9 @@ interface SavedObjectsManagementTypeDefinition { isImportableAndExportable?: boolean; defaultSearchField?: string; icon?: string; - getTitle?: (savedObject: SavedObject) => string; - getEditUrl?: (savedObject: SavedObject) => string; - getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string }; + getTitle?: (savedObject: SavedObject) => string; + getEditUrl?: (savedObject: SavedObject) => string; + getInAppUrl?: (savedObject: SavedObject) => { path: string; uiCapabilitiesPath: string }; } export interface SavedObjectsManagementDefinition { diff --git a/src/core/server/saved_objects/serialization/types.ts b/src/core/server/saved_objects/serialization/types.ts index aaf6f45c244ec..524c2c8ffae7a 100644 --- a/src/core/server/saved_objects/serialization/types.ts +++ b/src/core/server/saved_objects/serialization/types.ts @@ -50,7 +50,7 @@ export interface SavedObjectsRawDocSource { * scenario out of the box. */ interface SavedObjectDoc { - attributes: object; + attributes: unknown; id?: string; // NOTE: SavedObjectDoc is used for uncreated objects where `id` is optional type: string; namespace?: string; diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index d386eda1ce92a..c365706127c1f 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -47,7 +47,6 @@ import { } from '../saved_objects_client'; import { SavedObject, - SavedObjectAttributes, SavedObjectsBaseOptions, SavedObjectsFindOptions, SavedObjectsMigrationVersion, @@ -212,7 +211,7 @@ export class SavedObjectsRepository { * @property {array} [options.references=[]] - [{ name, type, id }] * @returns {promise} - { id, type, version, attributes } */ - public async create( + public async create( type: string, attributes: T, options: SavedObjectsCreateOptions = {} @@ -253,7 +252,7 @@ export class SavedObjectsRepository { body: raw._source, }); - return this._rawToSavedObject({ + return this._rawToSavedObject({ ...raw, ...response, }); @@ -276,7 +275,7 @@ export class SavedObjectsRepository { * @property {string} [options.namespace] * @returns {promise} - {saved_objects: [[{ id, type, version, references, attributes, error: { message } }]} */ - async bulkCreate( + async bulkCreate( objects: Array>, options: SavedObjectsCreateOptions = {} ): Promise> { @@ -463,7 +462,7 @@ export class SavedObjectsRepository { * @property {object} [options.hasReference] - { type, id } * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - async find({ + async find({ search, defaultSearchOperator = 'OR', searchFields, @@ -576,7 +575,7 @@ export class SavedObjectsRepository { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet( + async bulkGet( objects: SavedObjectsBulkGetObject[] = [], options: SavedObjectsBaseOptions = {} ): Promise> { @@ -647,7 +646,7 @@ export class SavedObjectsRepository { * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - async get( + async get( type: string, id: string, options: SavedObjectsBaseOptions = {} @@ -695,7 +694,7 @@ export class SavedObjectsRepository { * @property {array} [options.references] - [{ name, type, id }] * @returns {promise} */ - async update( + async update( type: string, id: string, attributes: Partial, @@ -752,7 +751,7 @@ export class SavedObjectsRepository { * @property {string} [options.namespace] * @returns {promise} - {saved_objects: [[{ id, type, version, references, attributes, error: { message } }]} */ - async bulkUpdate( + async bulkUpdate( objects: Array>, options: SavedObjectsBulkUpdateOptions = {} ): Promise> { @@ -971,7 +970,7 @@ export class SavedObjectsRepository { // includes the namespace, and we use this for migrating documents. However, we don't // want the namespace to be returned from the repository, as the repository scopes each // method transparently to the specified namespace. - private _rawToSavedObject(raw: SavedObjectsRawDoc): SavedObject { + private _rawToSavedObject(raw: SavedObjectsRawDoc): SavedObject { const savedObject = this._serializer.rawToSavedObject(raw); return omit(savedObject, 'namespace'); } diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index b0b2633646e10..70d69374ba8fe 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -20,7 +20,6 @@ import { ISavedObjectsRepository } from './lib'; import { SavedObject, - SavedObjectAttributes, SavedObjectReference, SavedObjectsMigrationVersion, SavedObjectsBaseOptions, @@ -49,7 +48,7 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { * * @public */ -export interface SavedObjectsBulkCreateObject { +export interface SavedObjectsBulkCreateObject { id?: string; type: string; attributes: T; @@ -62,7 +61,7 @@ export interface SavedObjectsBulkCreateObject +export interface SavedObjectsBulkUpdateObject extends Pick { /** The ID of this Saved Object, guaranteed to be unique for all objects of the same `type` */ id: string; @@ -76,7 +75,7 @@ export interface SavedObjectsBulkUpdateObject { +export interface SavedObjectsBulkResponse { saved_objects: Array>; } @@ -88,7 +87,7 @@ export interface SavedObjectsBulkResponse * * @public */ -export interface SavedObjectsFindResponse { +export interface SavedObjectsFindResponse { saved_objects: Array>; total: number; per_page: number; @@ -141,7 +140,7 @@ export interface SavedObjectsBulkGetObject { * * @public */ -export interface SavedObjectsBulkResponse { +export interface SavedObjectsBulkResponse { saved_objects: Array>; } @@ -149,7 +148,7 @@ export interface SavedObjectsBulkResponse * * @public */ -export interface SavedObjectsBulkUpdateResponse { +export interface SavedObjectsBulkUpdateResponse { saved_objects: Array>; } @@ -157,7 +156,7 @@ export interface SavedObjectsBulkUpdateResponse +export interface SavedObjectsUpdateResponse extends Omit, 'attributes' | 'references'> { attributes: Partial; references: SavedObjectReference[] | undefined; @@ -185,11 +184,7 @@ export class SavedObjectsClient { * @param attributes * @param options */ - async create( - type: string, - attributes: T, - options?: SavedObjectsCreateOptions - ) { + async create(type: string, attributes: T, options?: SavedObjectsCreateOptions) { return await this._repository.create(type, attributes, options); } @@ -199,7 +194,7 @@ export class SavedObjectsClient { * @param objects * @param options */ - async bulkCreate( + async bulkCreate( objects: Array>, options?: SavedObjectsCreateOptions ) { @@ -222,9 +217,7 @@ export class SavedObjectsClient { * * @param options */ - async find( - options: SavedObjectsFindOptions - ): Promise> { + async find(options: SavedObjectsFindOptions): Promise> { return await this._repository.find(options); } @@ -239,7 +232,7 @@ export class SavedObjectsClient { * { id: 'foo', type: 'index-pattern' } * ]) */ - async bulkGet( + async bulkGet( objects: SavedObjectsBulkGetObject[] = [], options: SavedObjectsBaseOptions = {} ): Promise> { @@ -253,7 +246,7 @@ export class SavedObjectsClient { * @param id - The ID of the SavedObject to retrieve * @param options */ - async get( + async get( type: string, id: string, options: SavedObjectsBaseOptions = {} @@ -268,7 +261,7 @@ export class SavedObjectsClient { * @param id * @param options */ - async update( + async update( type: string, id: string, attributes: Partial, @@ -282,7 +275,7 @@ export class SavedObjectsClient { * * @param objects */ - async bulkUpdate( + async bulkUpdate( objects: Array>, options?: SavedObjectsBulkUpdateOptions ): Promise> { diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index a4fde1765b7d3..9c204784b0aeb 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -33,7 +33,6 @@ export { SavedObjectsImportRetry, } from './import/types'; -import { SavedObjectAttributes } from '../../types'; import { LegacyConfig } from '../legacy'; export { SavedObjectAttributes, @@ -64,7 +63,7 @@ export interface SavedObjectsMigrationVersion { * * @public */ -export interface SavedObject { +export interface SavedObject { /** The ID of this Saved Object, guaranteed to be unique for all objects of the same `type` */ id: string; /** The type of Saved Object. Each plugin can define it's own custom Saved Object types. */ diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 053a60028fc5f..f717f30fdb0cf 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1462,7 +1462,7 @@ export interface RouteValidatorOptions { } // @public (undocumented) -export interface SavedObject { +export interface SavedObject { attributes: T; // (undocumented) error?: { @@ -1524,7 +1524,7 @@ export interface SavedObjectsBaseOptions { } // @public (undocumented) -export interface SavedObjectsBulkCreateObject { +export interface SavedObjectsBulkCreateObject { // (undocumented) attributes: T; // (undocumented) @@ -1546,19 +1546,19 @@ export interface SavedObjectsBulkGetObject { } // @public (undocumented) -export interface SavedObjectsBulkResponse { +export interface SavedObjectsBulkResponse { // (undocumented) saved_objects: Array>; } // @public (undocumented) -export interface SavedObjectsBulkResponse { +export interface SavedObjectsBulkResponse { // (undocumented) saved_objects: Array>; } // @public (undocumented) -export interface SavedObjectsBulkUpdateObject extends Pick { +export interface SavedObjectsBulkUpdateObject extends Pick { attributes: Partial; id: string; type: string; @@ -1570,7 +1570,7 @@ export interface SavedObjectsBulkUpdateOptions extends SavedObjectsBaseOptions { } // @public (undocumented) -export interface SavedObjectsBulkUpdateResponse { +export interface SavedObjectsBulkUpdateResponse { // (undocumented) saved_objects: Array>; } @@ -1579,18 +1579,18 @@ export interface SavedObjectsBulkUpdateResponse(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; - bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; - bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; - create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; + bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; + bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; + bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; + create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; // (undocumented) static errors: typeof SavedObjectsErrorHelpers; // (undocumented) errors: typeof SavedObjectsErrorHelpers; - find(options: SavedObjectsFindOptions): Promise>; - get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; - update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; + find(options: SavedObjectsFindOptions): Promise>; + get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; + update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; } // @public @@ -1772,7 +1772,7 @@ export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions { } // @public -export interface SavedObjectsFindResponse { +export interface SavedObjectsFindResponse { // (undocumented) page: number; // (undocumented) @@ -1951,10 +1951,10 @@ export interface SavedObjectsRawDoc { // @public (undocumented) export class SavedObjectsRepository { - bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; - bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; - bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; - create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; + bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; + bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; + bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; + create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; // Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts // // @internal @@ -1962,8 +1962,8 @@ export class SavedObjectsRepository { delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; // (undocumented) - find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; - get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; + find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; + get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{ id: string; type: string; @@ -1972,7 +1972,7 @@ export class SavedObjectsRepository { version: string; attributes: any; }>; - update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; + update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; } // @public @@ -2062,7 +2062,7 @@ export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { } // @public (undocumented) -export interface SavedObjectsUpdateResponse extends Omit, 'attributes' | 'references'> { +export interface SavedObjectsUpdateResponse extends Omit, 'attributes' | 'references'> { // (undocumented) attributes: Partial; // (undocumented) diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts index 0544a1806e09a..55e32b1e3bb37 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts @@ -45,7 +45,10 @@ export async function createOrUpgradeSavedConfig( }); // default to the attributes of the upgradeableConfig if available - const attributes = defaults({ buildNum }, upgradeableConfig ? upgradeableConfig.attributes : {}); + const attributes = defaults( + { buildNum }, + upgradeableConfig ? (upgradeableConfig.attributes as any) : {} + ); try { // create the new SavedConfig diff --git a/src/core/server/ui_settings/ui_settings_client.ts b/src/core/server/ui_settings/ui_settings_client.ts index 3c9c232bff280..a7e55d2b2da65 100644 --- a/src/core/server/ui_settings/ui_settings_client.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -185,7 +185,7 @@ export class UiSettingsClient implements IUiSettingsClient { autoCreateOrUpgradeIfMissing = true, }: ReadOptions = {}): Promise> { try { - const resp = await this.savedObjectsClient.get(this.type, this.id); + const resp = await this.savedObjectsClient.get>(this.type, this.id); return resp.attributes; } catch (error) { if (SavedObjectsErrorHelpers.isNotFoundError(error) && autoCreateOrUpgradeIfMissing) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants.ts index fe42e07912799..0820ebd371004 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants.ts @@ -23,6 +23,8 @@ export const DashboardConstants = { CREATE_NEW_DASHBOARD_URL: '/dashboard', ADD_EMBEDDABLE_ID: 'addEmbeddableId', ADD_EMBEDDABLE_TYPE: 'addEmbeddableType', + DASHBOARDS_ID: 'dashboards', + DASHBOARD_ID: 'dashboard', }; export function createDashboardEditUrl(id: string) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index efc8e8037e19a..253f9c77bd8e5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -83,7 +83,14 @@ export class DashboardPlugin implements Plugin { ); const { appMounted, appUnMounted, stop: stopUrlTracker } = createKbnUrlTracker({ baseUrl: core.http.basePath.prepend('/app/kibana'), - defaultSubUrl: '#/dashboards', + defaultSubUrl: `#${DashboardConstants.LANDING_PAGE_PATH}`, + shouldTrackUrlUpdate: pathname => { + const targetAppName = pathname.split('/')[1]; + return ( + targetAppName === DashboardConstants.DASHBOARDS_ID || + targetAppName === DashboardConstants.DASHBOARD_ID + ); + }, storageKey: 'lastUrl:dashboard', navLinkUpdater$: this.appStateUpdater, toastNotifications: core.notifications.toasts, @@ -150,15 +157,15 @@ export class DashboardPlugin implements Plugin { }; kibanaLegacy.registerLegacyApp({ ...app, - id: 'dashboard', + id: DashboardConstants.DASHBOARD_ID, // only register the updater in once app, otherwise all updates would happen twice updater$: this.appStateUpdater.asObservable(), navLinkId: 'kibana:dashboard', }); - kibanaLegacy.registerLegacyApp({ ...app, id: 'dashboards' }); + kibanaLegacy.registerLegacyApp({ ...app, id: DashboardConstants.DASHBOARDS_ID }); home.featureCatalogue.register({ - id: 'dashboard', + id: DashboardConstants.DASHBOARD_ID, title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { defaultMessage: 'Dashboard', }), diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/_discover.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/_discover.scss index 0da28e41579ae..62e7a96ed80cf 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/_discover.scss +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/_discover.scss @@ -206,7 +206,7 @@ discover-app { background-color: transparent; } -.dscField--noResults { +.dscFieldName--noResults { color: $euiColorDarkShade; } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/__snapshots__/field_name.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/__snapshots__/field_name.test.tsx.snap index bdb003771619c..23288fc5feb59 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/__snapshots__/field_name.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/__snapshots__/field_name.test.tsx.snap @@ -1,73 +1,109 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`FieldName renders a geo field, useShortDots is set to true 1`] = ` - - - - t.t.test - - + + + + +
+ + t.t.test + +
+ `; exports[`FieldName renders a number field by providing a field record, useShortDots is set to false 1`] = ` - - - + + + + +
- test.test.test - - + + test.test.test + +
+ `; exports[`FieldName renders a string field by providing fieldType and fieldName 1`] = ` - - - + + + + +
- test - - + + test + +
+ `; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.tsx index 95720bee38df8..54e1c1706a856 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.tsx @@ -18,7 +18,9 @@ */ import React from 'react'; import classNames from 'classnames'; -import { FieldIcon } from '../../../../../../../../../plugins/kibana_react/public'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { FieldIcon, FieldIconProps } from '../../../../../../../../../plugins/kibana_react/public'; import { shortenDottedString } from '../../../../../../../../../plugins/data/common/utils'; import { getFieldTypeName } from './field_type_name'; @@ -35,25 +37,35 @@ interface Props { fieldName?: string; fieldType?: string; useShortDots?: boolean; + fieldIconProps?: Omit; } -export function FieldName({ field, fieldName, fieldType, useShortDots }: Props) { +export function FieldName({ field, fieldName, fieldType, useShortDots, fieldIconProps }: Props) { const type = field ? String(field.type) : String(fieldType); const typeName = getFieldTypeName(type); const name = field ? String(field.name) : String(fieldName); const displayName = useShortDots ? shortenDottedString(name) : name; - const className = classNames({ - 'dscField--noResults': field ? !field.rowCount && !field.scripted : false, - // this is currently not styled, should display an icon - scripted: field ? field.scripted : false, + const noResults = field ? !field.rowCount && !field.scripted : false; + + const className = classNames('dscFieldName', { + 'dscFieldName--noResults': noResults, }); return ( - - - {displayName} - + + + + + + {displayName} + + ); } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_field_chooser.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_field_chooser.scss index fe13ac2fafa01..b05775c4ee95c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_field_chooser.scss +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/_field_chooser.scss @@ -10,7 +10,6 @@ .dscFieldName { color: $euiColorDarkShade; - padding-left: $euiSizeS; } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx index 96b8cc383888e..b6fd5ee60b8e2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.test.tsx @@ -31,14 +31,14 @@ const indexPattern1 = { attributes: { title: 'test1 title', }, -} as SavedObject; +} as SavedObject; const indexPattern2 = { id: 'test2', attributes: { title: 'test2 title', }, -} as SavedObject; +} as SavedObject; const defaultProps = { indexPatternList: [indexPattern1, indexPattern2], diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx index a4e8ee2ca3d8a..cca523ee2c1bd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/discover_index_pattern.tsx @@ -18,6 +18,7 @@ */ import React, { useState } from 'react'; import { SavedObject } from 'kibana/server'; +import { IndexPatternAttributes } from 'src/plugins/data/public'; import { I18nProvider } from '@kbn/i18n/react'; import { IndexPatternRef } from './types'; @@ -26,11 +27,11 @@ export interface DiscoverIndexPatternProps { /** * list of available index patterns, if length > 1, component offers a "change" link */ - indexPatternList: SavedObject[]; + indexPatternList: Array>; /** * currently selected index pattern, due to angular issues it's undefined at first rendering */ - selectedIndexPattern: SavedObject; + selectedIndexPattern: SavedObject; /** * triggered when user selects a new index pattern */ diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx index 7a78e89416361..5b13f6b3655c3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx @@ -87,7 +87,12 @@ export function DocViewTableRow({ )} - + {isCollapsible && ( diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx index 7cfc7c4dbc81c..bbb6bf26e5b31 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx @@ -24,6 +24,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { indexPatterns, DataPublicPluginStart, + IndexPatternAttributes, } from '../../../../../../../../../../plugins/data/public'; import { SavedObjectsClient, IUiSettingsClient } from '../../../../../../../../../../core/public'; import { MAX_SEARCH_SIZE } from '../../constants'; @@ -96,7 +97,7 @@ export class StepIndexPattern extends Component { - const { savedObjects } = await this.props.savedObjectsClient.find({ + const { savedObjects } = await this.props.savedObjectsClient.find({ type: 'index-pattern', fields: ['title'], perPage: 10000, diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts index 95e01f9c8db5b..ea9532964d6fe 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts @@ -20,7 +20,10 @@ import { get } from 'lodash'; import { getIndexPatterns, getSavedObjectsClient } from './plugin_services'; import { TimelionFunctionArgs } from '../../../../../plugins/timelion/common/types'; -import { indexPatterns as indexPatternsUtils } from '../../../../../plugins/data/public'; +import { + indexPatterns as indexPatternsUtils, + IndexPatternAttributes, +} from '../../../../../plugins/data/public'; export interface Location { min: number; @@ -53,7 +56,7 @@ export function getArgValueSuggestions() { } const indexPatternTitle = get(indexPatternArg, 'value.text'); - const { savedObjects } = await savedObjectsClient.find({ + const { savedObjects } = await savedObjectsClient.find({ type: 'index-pattern', fields: ['title'], search: `"${indexPatternTitle}"`, @@ -84,7 +87,7 @@ export function getArgValueSuggestions() { es: { async index(partial: string) { const search = partial ? `${partial}*` : '*'; - const resp = await savedObjectsClient.find({ + const resp = await savedObjectsClient.find({ type: 'index-pattern', fields: ['title', 'type'], search: `${search}`, diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts index 25aa77ec73579..cfb2960cfbb7c 100644 --- a/src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts @@ -18,7 +18,11 @@ */ import { VisSavedObject } from './visualize_embeddable'; -import { indexPatterns, IIndexPattern } from '../../../../../plugins/data/public'; +import { + indexPatterns, + IIndexPattern, + IndexPatternAttributes, +} from '../../../../../plugins/data/public'; import { getUISettings, getSavedObjects } from '../np_ready/public/services'; export async function getIndexPattern( @@ -32,7 +36,7 @@ export async function getIndexPattern( const defaultIndex = getUISettings().get('defaultIndex'); if (savedVis.vis.params.index_pattern) { - const indexPatternObjects = await savedObjectsClient.find({ + const indexPatternObjects = await savedObjectsClient.find({ type: 'index-pattern', fields: ['title', 'fields'], search: `"${savedVis.vis.params.index_pattern}"`, @@ -42,6 +46,9 @@ export async function getIndexPattern( return indexPattern; } - const savedObject = await savedObjectsClient.get('index-pattern', defaultIndex); + const savedObject = await savedObjectsClient.get( + 'index-pattern', + defaultIndex + ); return indexPatterns.getFromSavedObject(savedObject); } diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 7d93bc5b84a5c..7e9346cc3f291 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -35,3 +35,15 @@ export interface IIndexPattern { } >; } + +/** + * Use data plugin interface instead + * @deprecated + */ +export interface IndexPatternAttributes { + type: string; + fields: string; + title: string; + typeMeta: string; + timeFieldName?: string; +} diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index cbd4bfd348797..978f140eb1d26 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -263,6 +263,7 @@ export { IFieldSubType, ES_FIELD_TYPES, KBN_FIELD_TYPES, + IndexPatternAttributes, } from '../common'; /* diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts index 5f95b101302ef..acce5ed57683c 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -50,7 +50,7 @@ export class IndexPatternsService { private async refreshSavedObjectsCache() { this.savedObjectsCache = ( - await this.savedObjectsClient.find({ + await this.savedObjectsClient.find>({ type: 'index-pattern', fields: ['title'], perPage: 10000, diff --git a/src/plugins/data/public/index_patterns/lib/get_from_saved_object.ts b/src/plugins/data/public/index_patterns/lib/get_from_saved_object.ts index 60b2023f25609..1630a4547b7a1 100644 --- a/src/plugins/data/public/index_patterns/lib/get_from_saved_object.ts +++ b/src/plugins/data/public/index_patterns/lib/get_from_saved_object.ts @@ -17,17 +17,20 @@ * under the License. */ +import { SavedObject } from 'src/core/public'; import { get } from 'lodash'; -import { IIndexPattern } from '../..'; +import { IIndexPattern, IndexPatternAttributes } from '../..'; -export function getFromSavedObject(savedObject: any): IIndexPattern | undefined { +export function getFromSavedObject( + savedObject: SavedObject +): IIndexPattern | undefined { if (get(savedObject, 'attributes.fields') === undefined) { return; } return { id: savedObject.id, - fields: JSON.parse(savedObject.attributes.fields), + fields: JSON.parse(savedObject.attributes.fields!), title: savedObject.attributes.title, }; } diff --git a/src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts b/src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts index 6bef11e4fc46c..1e01d2452ce04 100644 --- a/src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts +++ b/src/plugins/data/public/ui/query_string_input/fetch_index_patterns.ts @@ -18,7 +18,7 @@ */ import { isEmpty } from 'lodash'; import { IUiSettingsClient, SavedObjectsClientContract } from 'src/core/public'; -import { indexPatterns } from '../..'; +import { indexPatterns, IndexPatternAttributes } from '../..'; export async function fetchIndexPatterns( savedObjectsClient: SavedObjectsClientContract, @@ -30,7 +30,7 @@ export async function fetchIndexPatterns( } const searchString = indexPatternStrings.map(string => `"${string}"`).join(' | '); - const indexPatternsFromSavedObjects = await savedObjectsClient.find({ + const indexPatternsFromSavedObjects = await savedObjectsClient.find({ type: 'index-pattern', fields: ['title', 'fields'], search: searchString, @@ -38,7 +38,7 @@ export async function fetchIndexPatterns( }); const exactMatches = indexPatternsFromSavedObjects.savedObjects.filter(savedObject => { - return indexPatternStrings.includes(savedObject.attributes.title as string); + return indexPatternStrings.includes(savedObject.attributes.title); }); const defaultIndex = uiSettings.get('defaultIndex'); @@ -46,7 +46,10 @@ export async function fetchIndexPatterns( const allMatches = exactMatches.length === indexPatternStrings.length ? exactMatches - : [...exactMatches, await savedObjectsClient.get('index-pattern', defaultIndex)]; + : [ + ...exactMatches, + await savedObjectsClient.get('index-pattern', defaultIndex), + ]; return allMatches.map(indexPatterns.getFromSavedObject); } diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 40d367138b60d..020c3c4c1192d 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -142,6 +142,7 @@ export { IFieldSubType, ES_FIELD_TYPES, KBN_FIELD_TYPES, + IndexPatternAttributes, } from '../common'; /** diff --git a/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts b/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts index 29cf2289fc5b3..bfe99730039d2 100644 --- a/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts +++ b/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts @@ -67,7 +67,7 @@ export interface AppLinkSchema { label: string; } -export interface SampleDatasetSchema { +export interface SampleDatasetSchema { id: string; name: string; description: string; @@ -83,7 +83,7 @@ export interface SampleDatasetSchema { // Kibana saved objects (index patter, visualizations, dashboard, ...) // Should provide a nice demo of Kibana's functionality with the sample data set - savedObjects: SavedObject[]; + savedObjects: Array>; dataIndices: DataIndexSchema[]; status?: string | undefined; statusMsg?: unknown; diff --git a/src/plugins/home/server/services/sample_data/sample_data_registry.ts b/src/plugins/home/server/services/sample_data/sample_data_registry.ts index aac680211e52e..b6d04c5c0b6a3 100644 --- a/src/plugins/home/server/services/sample_data/sample_data_registry.ts +++ b/src/plugins/home/server/services/sample_data/sample_data_registry.ts @@ -137,9 +137,9 @@ export class SampleDataRegistry { throw new Error(`Unable to find sample dataset with id: ${sampleDataId}`); } - const dashboard = sampleDataset.savedObjects.find((savedObject: SavedObject) => { + const dashboard = sampleDataset.savedObjects.find(savedObject => { return savedObject.id === dashboardId && savedObject.type === 'dashboard'; - }); + }) as SavedObject<{ panelsJSON: string }>; if (!dashboard) { throw new Error(`Unable to find dashboard with id: ${dashboardId}`); } diff --git a/src/plugins/kibana_react/public/field_icon/__snapshots__/field_icon.test.tsx.snap b/src/plugins/kibana_react/public/field_icon/__snapshots__/field_icon.test.tsx.snap index 870dbdc533267..cde6a625ac8e8 100644 --- a/src/plugins/kibana_react/public/field_icon/__snapshots__/field_icon.test.tsx.snap +++ b/src/plugins/kibana_react/public/field_icon/__snapshots__/field_icon.test.tsx.snap @@ -1,37 +1,159 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FieldIcon renders a blackwhite icon for a string 1`] = ` - `; -exports[`FieldIcon renders a colored icon for a number 1`] = ` - `; -exports[`FieldIcon renders an icon for an unknown type 1`] = ` - +`; + +exports[`FieldIcon renders known field types boolean is rendered 1`] = ` + +`; + +exports[`FieldIcon renders known field types conflict is rendered 1`] = ` + +`; + +exports[`FieldIcon renders known field types date is rendered 1`] = ` + +`; + +exports[`FieldIcon renders known field types geo_point is rendered 1`] = ` + +`; + +exports[`FieldIcon renders known field types geo_shape is rendered 1`] = ` + +`; + +exports[`FieldIcon renders known field types ip is rendered 1`] = ` + +`; + +exports[`FieldIcon renders known field types murmur3 is rendered 1`] = ` + +`; + +exports[`FieldIcon renders known field types nested is rendered 1`] = ` + +`; + +exports[`FieldIcon renders known field types number is rendered 1`] = ` + +`; + +exports[`FieldIcon renders known field types string is rendered 1`] = ` + `; exports[`FieldIcon renders with className if provided 1`] = ` - +`; + +exports[`FieldIcon supports same props as EuiToken 1`] = ` + `; diff --git a/src/plugins/kibana_react/public/field_icon/field_icon.test.tsx b/src/plugins/kibana_react/public/field_icon/field_icon.test.tsx index 90a858e31b4f3..51ff5f603ea37 100644 --- a/src/plugins/kibana_react/public/field_icon/field_icon.test.tsx +++ b/src/plugins/kibana_react/public/field_icon/field_icon.test.tsx @@ -18,24 +18,44 @@ */ import React from 'react'; import { shallow } from 'enzyme'; -import { FieldIcon } from './field_icon'; +import { FieldIcon, typeToEuiIconMap } from './field_icon'; -test('FieldIcon renders a blackwhite icon for a string', () => { - const component = shallow(); +const availableTypes = Object.keys(typeToEuiIconMap); + +describe('FieldIcon renders known field types', () => { + availableTypes.forEach(type => { + test(`${type} is rendered`, () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + }); +}); + +test('FieldIcon renders an icon for an unknown type', () => { + const component = shallow(); expect(component).toMatchSnapshot(); }); -test('FieldIcon renders a colored icon for a number', () => { - const component = shallow(); +test('FieldIcon supports same props as EuiToken', () => { + const component = shallow( + + ); expect(component).toMatchSnapshot(); }); -test('FieldIcon renders an icon for an unknown type', () => { - const component = shallow(); +test('FieldIcon changes fill when scripted is true', () => { + const component = shallow(); expect(component).toMatchSnapshot(); }); test('FieldIcon renders with className if provided', () => { - const component = shallow(); + const component = shallow(); expect(component).toMatchSnapshot(); }); diff --git a/src/plugins/kibana_react/public/field_icon/field_icon.tsx b/src/plugins/kibana_react/public/field_icon/field_icon.tsx index 2e199a7471a64..2da1eba31e254 100644 --- a/src/plugins/kibana_react/public/field_icon/field_icon.tsx +++ b/src/plugins/kibana_react/public/field_icon/field_icon.tsx @@ -17,14 +17,10 @@ * under the License. */ import React from 'react'; -import { euiPaletteColorBlind, EuiIcon } from '@elastic/eui'; -import { IconSize } from '@elastic/eui/src/components/icon/icon'; +import classNames from 'classnames'; +import { EuiToken, EuiTokenProps } from '@elastic/eui'; -interface IconMapEntry { - icon: string; - color: string; -} -interface FieldIconProps { +export interface FieldIconProps extends Omit { type: | 'boolean' | 'conflict' @@ -39,51 +35,50 @@ interface FieldIconProps { | string | 'nested'; label?: string; - size?: IconSize; - useColor?: boolean; - className?: string; + scripted?: boolean; } -const colors = euiPaletteColorBlind(); - // defaultIcon => a unknown datatype -const defaultIcon = { icon: 'questionInCircle', color: colors[0] }; +const defaultIcon = { iconType: 'questionInCircle', color: 'gray' }; -export const typeToEuiIconMap: Partial> = { - boolean: { icon: 'invert', color: colors[5] }, +export const typeToEuiIconMap: Partial> = { + boolean: { iconType: 'tokenBoolean' }, // icon for an index pattern mapping conflict in discover - conflict: { icon: 'alert', color: colors[8] }, - date: { icon: 'calendar', color: colors[7] }, - geo_point: { icon: 'globe', color: colors[2] }, - geo_shape: { icon: 'globe', color: colors[2] }, - ip: { icon: 'storage', color: colors[8] }, + conflict: { iconType: 'alert', color: 'euiVisColor9' }, + date: { iconType: 'tokenDate' }, + geo_point: { iconType: 'tokenGeo' }, + geo_shape: { iconType: 'tokenGeo' }, + ip: { iconType: 'tokenIP' }, // is a plugin's data type https://www.elastic.co/guide/en/elasticsearch/plugins/current/mapper-murmur3-usage.html - murmur3: { icon: 'document', color: colors[1] }, - number: { icon: 'number', color: colors[0] }, - _source: { icon: 'editorCodeBlock', color: colors[3] }, - string: { icon: 'string', color: colors[4] }, - nested: { icon: 'nested', color: colors[2] }, + murmur3: { iconType: 'tokenFile' }, + number: { iconType: 'tokenNumber' }, + _source: { iconType: 'editorCodeBlock', color: 'gray' }, + string: { iconType: 'tokenString' }, + nested: { iconType: 'tokenNested' }, }; /** - * Field icon used across the app + * Field token icon used across the app */ export function FieldIcon({ type, label, size = 's', - useColor = false, - className = undefined, + scripted, + className, + ...rest }: FieldIconProps) { - const euiIcon = typeToEuiIconMap[type] || defaultIcon; + const token = typeToEuiIconMap[type] || defaultIcon; return ( - ); } diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.test.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.test.ts index 4cf74d991ceb9..701154c06a2ff 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.test.ts @@ -38,7 +38,7 @@ describe('kbnUrlTracker', () => { let navLinkUpdaterSubject: BehaviorSubject<(app: AppBase) => { activeUrl?: string } | undefined>; let toastService: jest.Mocked; - function createTracker() { + function createTracker(shouldTrackUrlUpdate?: (pathname: string) => boolean) { urlTracker = createKbnUrlTracker({ baseUrl: '/app/test', defaultSubUrl: '#/start', @@ -57,6 +57,7 @@ describe('kbnUrlTracker', () => { ], navLinkUpdater$: navLinkUpdaterSubject, toastNotifications: toastService, + shouldTrackUrlUpdate, }); } @@ -82,44 +83,44 @@ describe('kbnUrlTracker', () => { }); test('set nav link to session storage value if defined', () => { - storage.setItem('storageKey', '#/deep/path'); + storage.setItem('storageKey', '#/start/deep/path'); createTracker(); - expect(getActiveNavLinkUrl()).toEqual('/app/test#/deep/path'); + expect(getActiveNavLinkUrl()).toEqual('/app/test#/start/deep/path'); }); test('set nav link to default if app gets mounted', () => { - storage.setItem('storageKey', '#/deep/path'); + storage.setItem('storageKey', '#/start/deep/path'); createTracker(); urlTracker.appMounted(); expect(getActiveNavLinkUrl()).toEqual('/app/test#/start'); }); test('keep nav link to default if path gets changed while app mounted', () => { - storage.setItem('storageKey', '#/deep/path'); + storage.setItem('storageKey', '#/start/deep/path'); createTracker(); urlTracker.appMounted(); - history.push('/deep/path/2'); + history.push('/start/deep/path/2'); expect(getActiveNavLinkUrl()).toEqual('/app/test#/start'); }); test('change nav link to last visited url within app after unmount', () => { createTracker(); urlTracker.appMounted(); - history.push('/deep/path/2'); - history.push('/deep/path/3'); + history.push('/start/deep/path/2'); + history.push('/start/deep/path/3'); urlTracker.appUnMounted(); - expect(getActiveNavLinkUrl()).toEqual('/app/test#/deep/path/3'); + expect(getActiveNavLinkUrl()).toEqual('/app/test#/start/deep/path/3'); }); test('unhash all urls that are recorded while app is mounted', () => { (unhashUrl as jest.Mock).mockImplementation(x => x + '?unhashed'); createTracker(); urlTracker.appMounted(); - history.push('/deep/path/2'); - history.push('/deep/path/3'); + history.push('/start/deep/path/2'); + history.push('/start/deep/path/3'); urlTracker.appUnMounted(); expect(unhashUrl).toHaveBeenCalledTimes(2); - expect(getActiveNavLinkUrl()).toEqual('/app/test#/deep/path/3?unhashed'); + expect(getActiveNavLinkUrl()).toEqual('/app/test#/start/deep/path/3?unhashed'); }); test('show warning and use hashed url if unhashing does not work', () => { @@ -128,17 +129,17 @@ describe('kbnUrlTracker', () => { }); createTracker(); urlTracker.appMounted(); - history.push('/deep/path/2'); + history.push('/start/deep/path/2'); urlTracker.appUnMounted(); - expect(getActiveNavLinkUrl()).toEqual('/app/test#/deep/path/2'); + expect(getActiveNavLinkUrl()).toEqual('/app/test#/start/deep/path/2'); expect(toastService.addDanger).toHaveBeenCalledWith('unhash broke'); }); test('change nav link back to default if app gets mounted again', () => { createTracker(); urlTracker.appMounted(); - history.push('/deep/path/2'); - history.push('/deep/path/3'); + history.push('/start/deep/path/2'); + history.push('/start/deep/path/3'); urlTracker.appUnMounted(); urlTracker.appMounted(); expect(getActiveNavLinkUrl()).toEqual('/app/test#/start'); @@ -151,11 +152,11 @@ describe('kbnUrlTracker', () => { }); test('update state param without overwriting rest of the url when app is not mounted', () => { - storage.setItem('storageKey', '#/deep/path?extrastate=1'); + storage.setItem('storageKey', '#/start/deep/path?extrastate=1'); createTracker(); state1Subject.next({ key1: 'abc' }); expect(getActiveNavLinkUrl()).toMatchInlineSnapshot( - `"/app/test#/deep/path?extrastate=1&state1=(key1:abc)"` + `"/app/test#/start/deep/path?extrastate=1&state1=(key1:abc)"` ); }); @@ -184,7 +185,45 @@ describe('kbnUrlTracker', () => { test('set url to storage when setActiveUrl was called', () => { createTracker(); - urlTracker.setActiveUrl('/deep/path/4'); - expect(storage.getItem('storageKey')).toEqual('#/deep/path/4'); + urlTracker.setActiveUrl('/start/deep/path/4'); + expect(storage.getItem('storageKey')).toEqual('#/start/deep/path/4'); + }); + + describe('shouldTrackUrlUpdate', () => { + test('change nav link when shouldTrackUrlUpdate is not overridden', () => { + storage.setItem('storageKey', '#/start/deep/path'); + createTracker(); + urlTracker.appMounted(); + history.push('/start/path'); + urlTracker.appUnMounted(); + expect(getActiveNavLinkUrl()).toEqual('/app/test#/start/path'); + }); + + test('not change nav link when shouldTrackUrlUpdate is not overridden', () => { + storage.setItem('storageKey', '#/start/deep/path'); + createTracker(); + urlTracker.appMounted(); + history.push('/setup/path/2'); + urlTracker.appUnMounted(); + expect(getActiveNavLinkUrl()).toEqual('/app/test#/start/deep/path'); + }); + + test('change nav link when shouldTrackUrlUpdate is overridden', () => { + storage.setItem('storageKey', '#/start/deep/path'); + createTracker(() => true); + urlTracker.appMounted(); + history.push('/setup/path/2'); + urlTracker.appUnMounted(); + expect(getActiveNavLinkUrl()).toEqual('/app/test#/setup/path/2'); + }); + + test('not change nav link when shouldTrackUrlUpdate is overridden', () => { + storage.setItem('storageKey', '#/start/deep/path'); + createTracker(() => false); + urlTracker.appMounted(); + history.push('/setup/path/2'); + urlTracker.appUnMounted(); + expect(getActiveNavLinkUrl()).toEqual('/app/test#/start/deep/path'); + }); }); }); diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts index 2edd135c184ec..b778535a2d428 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts @@ -58,6 +58,12 @@ export function createKbnUrlTracker({ toastNotifications, history, storage, + shouldTrackUrlUpdate = pathname => { + const currentAppName = defaultSubUrl.slice(2); // cut hash and slash symbols + const targetAppName = pathname.split('/')[1]; + + return currentAppName === targetAppName; + }, }: { /** * Base url of the current app. This will be used as a prefix for the @@ -82,7 +88,7 @@ export function createKbnUrlTracker({ stateUpdate$: Observable; }>; /** - * Key used to store the current sub url in session storage. This key should only be used for one active url tracker at any given ntime. + * Key used to store the current sub url in session storage. This key should only be used for one active url tracker at any given time. */ storageKey: string; /** @@ -101,6 +107,13 @@ export function createKbnUrlTracker({ * Storage object to use to persist currently active url. If this isn't provided, the browser wide session storage instance will be used. */ storage?: Storage; + /** + * Checks if pathname belongs to current app. It's used in history listener to define whether it's necessary to set pathname as active url or not. + * The default implementation compares the app name to the first part of pathname. Consumers can override this function for more complex cases. + * + * @param {string} pathname A location's pathname which comes to history listener + */ + shouldTrackUrlUpdate?: (pathname: string) => boolean; }): KbnUrlTracker { const historyInstance = history || createHashHistory(); const storageInstance = storage || sessionStorage; @@ -148,7 +161,9 @@ export function createKbnUrlTracker({ unsubscribe(); // track current hash when within app unsubscribeURLHistory = historyInstance.listen(location => { - setActiveUrl(location.pathname + location.search); + if (shouldTrackUrlUpdate(location.pathname)) { + setActiveUrl(location.pathname + location.search); + } }); } diff --git a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx index b503392c9827f..81600e9f68634 100644 --- a/src/plugins/saved_objects/public/finder/saved_object_finder.tsx +++ b/src/plugins/saved_objects/public/finder/saved_object_finder.tsx @@ -43,14 +43,13 @@ import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; import { i18n } from '@kbn/i18n'; import { - SavedObjectAttributes, SimpleSavedObject, CoreStart, IUiSettingsClient, SavedObjectsStart, -} from '../../../../core/public'; +} from 'src/core/public'; -export interface SavedObjectMetaData { +export interface SavedObjectMetaData { type: string; name: string; getIconForSavedObject(savedObject: SimpleSavedObject): IconType; @@ -59,12 +58,17 @@ export interface SavedObjectMetaData { includeFields?: string[]; } +interface FinderAttributes { + title?: string; + type: string; +} + interface SavedObjectFinderState { items: Array<{ title: string | null; - id: SimpleSavedObject['id']; - type: SimpleSavedObject['type']; - savedObject: SimpleSavedObject; + id: SimpleSavedObject['id']; + type: SimpleSavedObject['type']; + savedObject: SimpleSavedObject; }>; query: string; isFetchingItems: boolean; @@ -78,13 +82,13 @@ interface SavedObjectFinderState { interface BaseSavedObjectFinder { onChoose?: ( - id: SimpleSavedObject['id'], - type: SimpleSavedObject['type'], + id: SimpleSavedObject['id'], + type: SimpleSavedObject['type'], name: string, - savedObject: SimpleSavedObject + savedObject: SimpleSavedObject ) => void; noItemsMessage?: React.ReactNode; - savedObjectMetaData: Array>; + savedObjectMetaData: Array>; showFilter?: boolean; } @@ -128,7 +132,7 @@ class SavedObjectFinderUi extends React.Component< .reduce((allFields, currentFields) => allFields.concat(currentFields), ['title']); const perPage = this.props.uiSettings.get('savedObjects:listingLimit'); - const resp = await this.props.savedObjects.client.find({ + const resp = await this.props.savedObjects.client.find({ type: Object.keys(metaDataMap), fields: [...new Set(fields)], search: query ? `${query}*` : undefined, @@ -163,6 +167,7 @@ class SavedObjectFinderUi extends React.Component< id, type, } = savedObject; + return { title: typeof title === 'string' ? title : '', id, @@ -208,7 +213,7 @@ class SavedObjectFinderUi extends React.Component< ); } - private getSavedObjectMetaDataMap(): Record> { + private getSavedObjectMetaDataMap(): Record { return this.props.savedObjectMetaData.reduce( (map, metaData) => ({ ...map, [metaData.type]: metaData }), {} @@ -470,7 +475,7 @@ class SavedObjectFinderUi extends React.Component< currentSavedObjectMetaData || ({ getIconForSavedObject: () => 'document', - } as Pick, 'getIconForSavedObject'>) + } as Pick, 'getIconForSavedObject'>) ).getIconForSavedObject(item.savedObject); return ( >({ type: this.lowercaseType, search: search ? `${search}*` : undefined, perPage: size, diff --git a/src/plugins/share/server/routes/lib/short_url_lookup.test.ts b/src/plugins/share/server/routes/lib/short_url_lookup.test.ts index 87e2b7b726e59..2b33489eb27c9 100644 --- a/src/plugins/share/server/routes/lib/short_url_lookup.test.ts +++ b/src/plugins/share/server/routes/lib/short_url_lookup.test.ts @@ -17,9 +17,10 @@ * under the License. */ -import { shortUrlLookupProvider, ShortUrlLookupService } from './short_url_lookup'; -import { SavedObjectsClientContract, Logger } from 'kibana/server'; -import { SavedObjectsClient } from '../../../../../core/server'; +import { shortUrlLookupProvider, ShortUrlLookupService, UrlAttributes } from './short_url_lookup'; +import { SavedObjectsClientContract, SavedObject } from 'kibana/server'; + +import { savedObjectsClientMock, loggingServiceMock } from '../../../../../core/server/mocks'; describe('shortUrlLookupProvider', () => { const ID = 'bf00ad16941fc51420f91a93428b27a0'; @@ -31,15 +32,10 @@ describe('shortUrlLookupProvider', () => { let shortUrl: ShortUrlLookupService; beforeEach(() => { - savedObjects = ({ - get: jest.fn(), - create: jest.fn(() => Promise.resolve({ id: ID })), - update: jest.fn(), - errors: SavedObjectsClient.errors, - } as unknown) as jest.Mocked; - + savedObjects = savedObjectsClientMock.create(); + savedObjects.create.mockResolvedValue({ id: ID } as SavedObject); deps = { savedObjects }; - shortUrl = shortUrlLookupProvider({ logger: ({ warn: () => {} } as unknown) as Logger }); + shortUrl = shortUrlLookupProvider({ logger: loggingServiceMock.create().get() }); }); describe('generateUrlId', () => { @@ -55,13 +51,13 @@ describe('shortUrlLookupProvider', () => { const [type, attributes, options] = savedObjects.create.mock.calls[0]; expect(type).toEqual(TYPE); - expect(Object.keys(attributes).sort()).toEqual([ + expect(Object.keys(attributes as UrlAttributes).sort()).toEqual([ 'accessCount', 'accessDate', 'createDate', 'url', ]); - expect(attributes.url).toEqual(URL); + expect((attributes as UrlAttributes).url).toEqual(URL); expect(options!.id).toEqual(ID); }); @@ -72,13 +68,13 @@ describe('shortUrlLookupProvider', () => { const [type, attributes] = savedObjects.create.mock.calls[0]; expect(type).toEqual(TYPE); - expect(Object.keys(attributes).sort()).toEqual([ + expect(Object.keys(attributes as UrlAttributes).sort()).toEqual([ 'accessCount', 'accessDate', 'createDate', 'url', ]); - expect(attributes.url).toEqual(URL); + expect((attributes as UrlAttributes).url).toEqual(URL); }); it('gracefully handles version conflict', async () => { @@ -119,7 +115,7 @@ describe('shortUrlLookupProvider', () => { expect(type).toEqual(TYPE); expect(id).toEqual(ID); expect(Object.keys(attributes).sort()).toEqual(['accessCount', 'accessDate']); - expect(attributes.accessCount).toEqual(3); + expect((attributes as UrlAttributes).accessCount).toEqual(3); }); }); }); diff --git a/src/plugins/share/server/routes/lib/short_url_lookup.ts b/src/plugins/share/server/routes/lib/short_url_lookup.ts index 0d8a9c86621de..65fa33a940fac 100644 --- a/src/plugins/share/server/routes/lib/short_url_lookup.ts +++ b/src/plugins/share/server/routes/lib/short_url_lookup.ts @@ -27,13 +27,20 @@ export interface ShortUrlLookupService { getUrl(url: string, deps: { savedObjects: SavedObjectsClientContract }): Promise; } +export interface UrlAttributes { + url: string; + accessCount: number; + createDate: number; + accessDate: number; +} + export function shortUrlLookupProvider({ logger }: { logger: Logger }): ShortUrlLookupService { async function updateMetadata( - doc: SavedObject, + doc: SavedObject, { savedObjects }: { savedObjects: SavedObjectsClientContract } ) { try { - await savedObjects.update('url', doc.id, { + await savedObjects.update('url', doc.id, { accessDate: new Date().valueOf(), accessCount: get(doc, 'attributes.accessCount', 0) + 1, }); @@ -53,7 +60,7 @@ export function shortUrlLookupProvider({ logger }: { logger: Logger }): ShortUrl const { isConflictError } = savedObjects.errors; try { - const doc = await savedObjects.create( + const doc = await savedObjects.create( 'url', { url, @@ -75,7 +82,7 @@ export function shortUrlLookupProvider({ logger }: { logger: Logger }): ShortUrl }, async getUrl(id, { savedObjects }) { - const doc = await savedObjects.get('url', id); + const doc = await savedObjects.get('url', id); updateMetadata(doc, { savedObjects }); return doc.attributes.url; diff --git a/x-pack/legacy/plugins/canvas/public/lib/es_service.ts b/x-pack/legacy/plugins/canvas/public/lib/es_service.ts index 2fa1bf94ad669..32f4fe041423c 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/es_service.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/es_service.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { IndexPatternAttributes } from 'src/plugins/data/public'; + import { API_ROUTE } from '../../common/lib/constants'; // @ts-ignore untyped local import { fetch } from '../../common/lib/fetch'; @@ -44,7 +46,7 @@ export const getFields = (index = '_all') => { export const getIndices = () => getSavedObjectsClient() - .find({ + .find({ type: 'index-pattern', fields: ['title'], searchFields: ['title'], @@ -62,7 +64,7 @@ export const getDefaultIndex = () => { return defaultIndexId ? getSavedObjectsClient() - .get('index-pattern', defaultIndexId) + .get('index-pattern', defaultIndexId) .then(defaultIndex => defaultIndex.attributes.title) .catch(err => notify.error(err, { title: strings.getDefaultIndexFetchErrorMessage() })) : Promise.resolve(''); diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx b/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx index 6152f33350917..f2a4c28afcdae 100644 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx +++ b/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx @@ -237,10 +237,18 @@ export function FieldEditor({ renderOption={(option, searchValue, contentClassName) => { const { type, label } = option; return ( - - {' '} - {label} - + + + + + + {label} + + ); }} compressed diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_icon.tsx b/x-pack/legacy/plugins/graph/public/components/field_manager/field_icon.tsx deleted file mode 100644 index 0c099135f631d..0000000000000 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_icon.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 React from 'react'; -import { ICON_TYPES, euiPaletteColorBlind, EuiIcon } from '@elastic/eui'; - -function stringToNum(s: string) { - return Array.from(s).reduce((acc, ch) => acc + ch.charCodeAt(0), 1); -} - -function getIconForDataType(dataType: string) { - const icons: Partial>> = { - boolean: 'invert', - date: 'calendar', - geo_point: 'globe', - ip: 'storage', - }; - return icons[dataType] || ICON_TYPES.find(t => t === dataType) || 'document'; -} - -export function getColorForDataType(type: string) { - const iconType = getIconForDataType(type); - const colors = euiPaletteColorBlind(); - const colorIndex = stringToNum(iconType) % colors.length; - return colors[colorIndex]; -} - -export type UnwrapArray = T extends Array ? P : T; - -export function FieldIcon({ type }: { type: string }) { - const iconType = getIconForDataType(type); - - return ; -} diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx b/x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx index b38e3f8430980..30f1fcffd4f67 100644 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx +++ b/x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx @@ -122,7 +122,7 @@ function toOptions( .filter(field => isExplorable(field) || field.selected) .map(field => ({ label: field.name, - prepend: , + prepend: , checked: field.selected ? 'on' : undefined, })) ); diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_loader.ts b/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_loader.ts index 184fbc01eb96f..d9bb119006e78 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_loader.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_loader.ts @@ -52,7 +52,7 @@ export function createSavedWorkspacesLoader( }, find: (searchString: string, size: number = 100) => { return savedObjectsClient - .find({ + .find>({ type: SavedWorkspace.type, search: searchString ? `${searchString}*` : undefined, perPage: size, diff --git a/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/_index.scss b/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/_index.scss index 2c03180256db8..411ed9fca4191 100644 --- a/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/_index.scss +++ b/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/_index.scss @@ -96,12 +96,12 @@ } } - &--toggle { - padding-left: 0; + &--multiField { + padding-left: $euiSizeL; } - &--multiField { - padding-left: $euiSizeS; + &--toggle { + padding-left: 0; } } } diff --git a/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/chained_multifields_warning.tsx b/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/chained_multifields_warning.tsx new file mode 100644 index 0000000000000..5e091edf3c75b --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/chained_multifields_warning.tsx @@ -0,0 +1,31 @@ +/* + * 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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiCallOut, EuiCode } from '@elastic/eui'; + +export const ChainedMultifieldsWarning = () => ( + +

+ copy_to, + }} + /> +

+
+); diff --git a/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx b/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx index 4c1c8bc1da114..fca40513b4e2b 100644 --- a/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx +++ b/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx @@ -21,6 +21,7 @@ import { CHILD_FIELD_INDENT_SIZE, LEFT_PADDING_SIZE_FIELD_ITEM_WRAPPER, } from '../../../constants'; +import { ChainedMultifieldsWarning } from '../../chained_multifields_warning'; import { FieldsList } from './fields_list'; import { CreateField } from './create_field'; import { DeleteFieldProvider } from './delete_field_provider'; @@ -33,6 +34,7 @@ interface Props { isHighlighted: boolean; isDimmed: boolean; isLastItem: boolean; + isChainedMultifieldsWarningVisible: boolean; childFieldsArray: NormalizedField[]; maxNestedDepth: number; addField(): void; @@ -49,6 +51,7 @@ function FieldListItemComponent( isDimmed, isCreateFieldFormVisible, areActionButtonsVisible, + isChainedMultifieldsWarningVisible, isLastItem, childFieldsArray, maxNestedDepth, @@ -274,6 +277,8 @@ function FieldListItemComponent( + {isExpanded && isChainedMultifieldsWarningVisible && } + {Boolean(childFieldsArray.length) && isExpanded && ( )} diff --git a/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item_container.tsx b/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item_container.tsx index cff2d294fead9..f2d1d4ebc9a56 100644 --- a/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item_container.tsx +++ b/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item_container.tsx @@ -26,11 +26,22 @@ export const FieldsListItemContainer = ({ fieldId, treeDepth, isLastItem }: Prop const getField = (id: string) => byId[id]; const field: NormalizedField = getField(fieldId); + const parentField: NormalizedField | undefined = + field.parentId === undefined ? undefined : getField(field.parentId); const { childFields } = field; const isHighlighted = fieldToEdit === fieldId; const isDimmed = status === 'editingField' && fieldToEdit !== fieldId; const isCreateFieldFormVisible = status === 'creatingField' && fieldToAddFieldTo === fieldId; const areActionButtonsVisible = status === 'idle'; + + let isChainedMultifieldsWarningVisible = false; + // We add "!Boolean(parentField?.hasMultiFields)" as we only want to show a callOut at the "root" of the nested multi-fields + if (field.hasMultiFields && !Boolean(parentField?.hasMultiFields)) { + isChainedMultifieldsWarningVisible = field + .childFields!.map(getField) + .some(childField => Boolean(childField?.hasMultiFields)); + } + const childFieldsArray = useMemo( () => (childFields !== undefined ? childFields.map(getField) : []), [childFields] @@ -64,6 +75,7 @@ export const FieldsListItemContainer = ({ fieldId, treeDepth, isLastItem }: Prop isDimmed={isDimmed} isCreateFieldFormVisible={isCreateFieldFormVisible} areActionButtonsVisible={areActionButtonsVisible} + isChainedMultifieldsWarningVisible={isChainedMultifieldsWarningVisible} isLastItem={isLastItem} childFieldsArray={childFieldsArray} maxNestedDepth={maxNestedDepth} diff --git a/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts b/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts index 337554ab5fa5a..e8f63265d378f 100644 --- a/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts +++ b/x-pack/legacy/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts @@ -54,12 +54,11 @@ export const getFieldMeta = (field: Field, isMultiField?: boolean): FieldMeta => Boolean(field[childFieldsName!]) && Object.keys(field[childFieldsName!]!).length > 0; - const canHaveMultiFields = isMultiField ? false : childFieldsName === 'fields'; - const hasMultiFields = isMultiField - ? false - : canHaveMultiFields && - Boolean(field[childFieldsName!]) && - Object.keys(field[childFieldsName!]!).length > 0; + const canHaveMultiFields = childFieldsName === 'fields'; + const hasMultiFields = + canHaveMultiFields && + Boolean(field[childFieldsName!]) && + Object.keys(field[childFieldsName!]!).length > 0; return { childFieldsName, diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap index 5593a1af00d70..8bbe49b2e0d7f 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap @@ -1,10 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`LensFieldIcon accepts FieldIcon props 1`] = ` + +`; + exports[`LensFieldIcon renders properly 1`] = ` `; diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_datapanel.scss b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_datapanel.scss index ed39beeb7d088..77d4b41a0413c 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_datapanel.scss +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_datapanel.scss @@ -52,3 +52,16 @@ @include euiFormControlLayoutPadding(1, 'right'); @include euiFormControlLayoutPadding(1, 'left'); } + +.lnsInnerIndexPatternDataPanel__filterType { + padding: $euiSizeS; +} + +.lnsInnerIndexPatternDataPanel__filterTypeInner { + display: flex; + align-items: center; + + .lnsFieldListPanel__fieldIcon { + margin-right: $euiSizeS; + } +} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_field_item.scss b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_field_item.scss index 54f9a3787466d..89f6bbf908419 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_field_item.scss +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/_field_item.scss @@ -14,7 +14,7 @@ } .lnsFieldItem--missing { - background: lightOrDarkTheme(transparentize($euiColorMediumShade, .9), $euiColorEmptyShade); + background: lightOrDarkTheme(transparentize($euiColorMediumShade, 0.9), $euiColorEmptyShade); color: $euiColorDarkShade; } @@ -24,10 +24,10 @@ display: flex; align-items: flex-start; transition: box-shadow $euiAnimSpeedFast $euiAnimSlightResistance, - background-color $euiAnimSpeedFast $euiAnimSlightResistance; // sass-lint:disable-line indentation + background-color $euiAnimSpeedFast $euiAnimSlightResistance; // sass-lint:disable-line indentation .lnsFieldItem__name { - margin-left: $euiSizeXS; + margin-left: $euiSizeS; flex-grow: 1; } @@ -37,7 +37,8 @@ } .lnsFieldListPanel__fieldIcon { - margin-top: 2px; + margin-top: $euiSizeXS / 2; + margin-right: $euiSizeXS / 2; } .lnsFieldItem__infoIcon { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 3231ab7d7ff12..69982aed78b40 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -384,6 +384,7 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ data-test-subj="lnsIndexPatternTypeFilterOptions" items={(availableFieldTypes as DataType[]).map(type => ( - {fieldTypeNames[type]} + + {fieldTypeNames[type]} + ))} /> diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx index 46d7233ba9587..77435fcdf3eed 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx @@ -169,6 +169,7 @@ export function FieldSelect({ diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_icon.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_icon.test.tsx deleted file mode 100644 index 6b12bb5feef1b..0000000000000 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_icon.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - */ - -/* - * 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 { shallow } from 'enzyme'; -import React from 'react'; -import { FieldIcon } from './field_icon'; - -describe('FieldIcon', () => { - it('should render icons', () => { - expect(shallow()).toMatchInlineSnapshot(` - - `); - expect(shallow()).toMatchInlineSnapshot(` - - `); - expect(shallow()).toMatchInlineSnapshot(` - - `); - expect(shallow()).toMatchInlineSnapshot(` - - `); - expect(shallow()).toMatchInlineSnapshot(` - - `); - }); -}); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_icon.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_icon.tsx deleted file mode 100644 index 796f200bffd97..0000000000000 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_icon.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 React from 'react'; -import { ICON_TYPES, euiPaletteColorBlind, EuiIcon } from '@elastic/eui'; -import classNames from 'classnames'; -import { DataType } from '../types'; - -function stringToNum(s: string) { - return Array.from(s).reduce((acc, ch) => acc + ch.charCodeAt(0), 1); -} - -function getIconForDataType(dataType: string) { - const icons: Partial>> = { - boolean: 'invert', - date: 'calendar', - ip: 'ip', - }; - return icons[dataType] || ICON_TYPES.find(t => t === dataType) || 'empty'; -} - -export function getColorForDataType(type: string) { - const iconType = getIconForDataType(type); - const colors = euiPaletteColorBlind(); - const colorIndex = stringToNum(iconType) % colors.length; - return colors[colorIndex]; -} - -export type UnwrapArray = T extends Array ? P : T; - -export function FieldIcon({ type }: { type: DataType }) { - const iconType = getIconForDataType(type); - - const classes = classNames( - 'lnsFieldListPanel__fieldIcon', - `lnsFieldListPanel__fieldIcon--${type}` - ); - - return ; -} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_item.tsx index 0271d2ca021c5..94d644e6590e1 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -46,7 +46,7 @@ import { DragDrop } from '../drag_drop'; import { DatasourceDataPanelProps, DataType } from '../types'; import { BucketedAggregation, FieldStatsResponse } from '../../../../../plugins/lens/common'; import { IndexPattern, IndexPatternField } from './types'; -import { getColorForDataType, LensFieldIcon } from './lens_field_icon'; +import { LensFieldIcon } from './lens_field_icon'; import { trackUiEvent } from '../lens_ui_telemetry'; export interface FieldItemProps { @@ -294,11 +294,6 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { ); } - const euiButtonColor = - field.type === 'string' ? 'accent' : field.type === 'number' ? 'secondary' : 'primary'; - const euiTextColor = - field.type === 'string' ? 'accent' : field.type === 'number' ? 'secondary' : 'default'; - const fromDate = DateMath.parse(dateRange.fromDate); const toDate = DateMath.parse(dateRange.toDate); @@ -391,8 +386,6 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { const specId = i18n.translate('xpack.lens.indexPattern.fieldStatsCountLabel', { defaultMessage: 'Count', }); - const expectedColor = getColorForDataType(field.type); - const seriesColors = expectedColor ? [expectedColor] : undefined; if (field.type === 'date') { return wrapInPopover( @@ -429,7 +422,6 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { yAccessors={['count']} xScaleType={ScaleType.Time} yScaleType={ScaleType.Linear} - customSeriesColors={seriesColors} timeZone="local" /> @@ -453,7 +445,6 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { yAccessors={['count']} xScaleType={ScaleType.Linear} yScaleType={ScaleType.Linear} - customSeriesColors={seriesColors} /> ); @@ -486,7 +477,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { )} - + {Math.round((topValue.count / props.sampledValues!) * 100)}% @@ -497,7 +488,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { value={topValue.count / props.sampledValues!} max={1} size="s" - color={euiButtonColor} + color="accent" /> ); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx index 961e22380bdca..317ce8f032f94 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx @@ -11,19 +11,14 @@ */ import React from 'react'; import { shallow } from 'enzyme'; -import { LensFieldIcon, getColorForDataType } from './lens_field_icon'; +import { LensFieldIcon } from './lens_field_icon'; test('LensFieldIcon renders properly', () => { const component = shallow(); expect(component).toMatchSnapshot(); }); -test('LensFieldIcon getColorForDataType for a valid type', () => { - const color = getColorForDataType('date'); - expect(color).toEqual('#DA8B45'); -}); - -test('LensFieldIcon getColorForDataType for an invalid type', () => { - const color = getColorForDataType('invalid'); - expect(color).toEqual('#54B399'); +test('LensFieldIcon accepts FieldIcon props', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx index 2e6a5fcd8115f..06eda73748cef 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx @@ -5,26 +5,16 @@ */ import React from 'react'; -import { euiPaletteColorBlind } from '@elastic/eui'; -import { FieldIcon, typeToEuiIconMap } from '../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon, FieldIconProps } from '../../../../../../src/plugins/kibana_react/public'; import { DataType } from '../types'; import { normalizeOperationDataType } from './utils'; -export function getColorForDataType(type: string) { - const iconMap = typeToEuiIconMap[normalizeOperationDataType(type as DataType)]; - if (iconMap) { - return iconMap.color; - } - return euiPaletteColorBlind()[0]; -} - -export function LensFieldIcon({ type }: { type: DataType }) { +export function LensFieldIcon({ type, ...rest }: FieldIconProps & { type: DataType }) { return ( ); } diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap b/x-pack/legacy/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap index f37dfdd879c5b..d0cdbe7243abe 100644 --- a/x-pack/legacy/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap @@ -23,7 +23,7 @@ exports[`Should remove selected fields from selectable 1`] = ` id="addTooltipFieldPopover" isOpen={false} ownFocus={true} - panelPaddingSize="m" + panelPaddingSize="none" > , "value": "@timestamp", }, ] } + searchProps={ + Object { + "compressed": true, + } + } searchable={true} singleSelection={false} > @@ -88,7 +93,7 @@ exports[`Should render 1`] = ` id="addTooltipFieldPopover" isOpen={false} ownFocus={true} - panelPaddingSize="m" + panelPaddingSize="none" > , "value": "@timestamp", }, Object { "label": "custom label for prop1", "prepend": , "value": "prop1", }, Object { "label": "prop2", "prepend": , "value": "prop2", }, ] } + searchProps={ + Object { + "compressed": true, + } + } searchable={true} singleSelection={false} > diff --git a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js b/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js index bddb74596f4ef..07bc54663c1d8 100644 --- a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js +++ b/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js @@ -39,7 +39,10 @@ function getOptions(fields, selectedFields) { .map(field => { return { value: field.name, - prepend: 'type' in field ? : null, + prepend: + 'type' in field ? ( + + ) : null, label: 'label' in field ? field.label : field.name, }; }) @@ -127,7 +130,12 @@ export class AddTooltipFieldPopover extends Component { return ( - + {(list, search) => (
{search} @@ -161,6 +169,7 @@ export class AddTooltipFieldPopover extends Component { button={this._renderAddButton()} isOpen={this.state.isPopoverOpen} closePopover={this._closePopover} + panelPaddingSize="none" ownFocus > {this._renderContent()} diff --git a/x-pack/legacy/plugins/maps/public/components/single_field_select.js b/x-pack/legacy/plugins/maps/public/components/single_field_select.js index 7351ce7691a82..98e33454b041b 100644 --- a/x-pack/legacy/plugins/maps/public/components/single_field_select.js +++ b/x-pack/legacy/plugins/maps/public/components/single_field_select.js @@ -8,7 +8,7 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import React from 'react'; -import { EuiComboBox, EuiHighlight } from '@elastic/eui'; +import { EuiComboBox, EuiHighlight, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; function fieldsToOptions(fields) { @@ -30,11 +30,14 @@ function fieldsToOptions(fields) { function renderOption(option, searchValue, contentClassName) { return ( - - -   - {option.label} - + + + + + + {option.label} + + ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js index a32c2ce04d735..cf0ec5589d6bc 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js @@ -7,18 +7,21 @@ import PropTypes from 'prop-types'; import React from 'react'; -import { EuiComboBox, EuiHighlight } from '@elastic/eui'; +import { EuiComboBox, EuiHighlight, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FIELD_ORIGIN } from '../../../../../common/constants'; import { i18n } from '@kbn/i18n'; import { FieldIcon } from '../../../../../../../../../src/plugins/kibana_react/public'; function renderOption(option, searchValue, contentClassName) { return ( - - -   - {option.label} - + + + + + + {option.label} + + ); } diff --git a/x-pack/legacy/plugins/ml/common/types/kibana.ts b/x-pack/legacy/plugins/ml/common/types/kibana.ts index d647bd882162b..4a2edfebd1bac 100644 --- a/x-pack/legacy/plugins/ml/common/types/kibana.ts +++ b/x-pack/legacy/plugins/ml/common/types/kibana.ts @@ -6,7 +6,8 @@ // custom edits or fixes for default kibana types which are incomplete -import { SavedObjectAttributes, SimpleSavedObject } from 'kibana/public'; +import { SimpleSavedObject } from 'kibana/public'; +import { IndexPatternAttributes } from 'src/plugins/data/common'; export type IndexPatternTitle = string; @@ -17,8 +18,9 @@ export interface Route { k7Breadcrumbs: () => any; } -export type IndexPatternSavedObject = SimpleSavedObject; -export type SavedSearchSavedObject = SimpleSavedObject; +export type IndexPatternSavedObject = SimpleSavedObject; +// TODO define saved object type +export type SavedSearchSavedObject = SimpleSavedObject; export function isSavedSearchSavedObject( ss: SavedSearchSavedObject | null diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts index fa0ed34dca622..9c60b84b16bdf 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts @@ -61,7 +61,7 @@ export const checkForSavedObjects = async (objects: KibanaObjects): Promise { const acc = await prevPromise; - const { savedObjects } = await savedObjectsClient.find({ + const { savedObjects } = await savedObjectsClient.find({ type, perPage: 1000, }); diff --git a/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts index 88b56b2329ae6..220d707ddd665 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts @@ -5,11 +5,12 @@ */ import { i18n } from '@kbn/i18n'; -import { Query } from 'src/plugins/data/public'; import { IndexPattern, IIndexPattern, IndexPatternsContract, + Query, + IndexPatternAttributes, } from '../../../../../../../src/plugins/data/public'; import { getToastNotifications, getSavedObjectsClient } from './dependency_cache'; import { IndexPatternSavedObject, SavedSearchSavedObject } from '../../../common/types/kibana'; @@ -22,7 +23,7 @@ export function loadIndexPatterns(indexPatterns: IndexPatternsContract) { indexPatternsContract = indexPatterns; const savedObjectsClient = getSavedObjectsClient(); return savedObjectsClient - .find({ + .find({ type: 'index-pattern', fields: ['id', 'title', 'type', 'fields'], perPage: 10000, diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.ts index b62e44c299a2d..553de75e38e05 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -8,6 +8,7 @@ import fs from 'fs'; import Boom from 'boom'; import numeral from '@elastic/numeral'; import { CallAPIOptions, RequestHandlerContext, SavedObjectsClientContract } from 'kibana/server'; +import { IndexPatternAttributes } from 'src/plugins/data/server'; import { merge } from 'lodash'; import { MlJob } from '../../../common/types/jobs'; import { @@ -532,7 +533,10 @@ export class DataRecognizer { } async loadIndexPatterns() { - return await this.savedObjectsClient.find({ type: 'index-pattern', perPage: 1000 }); + return await this.savedObjectsClient.find({ + type: 'index-pattern', + perPage: 1000, + }); } // returns a id based on an index pattern name @@ -620,7 +624,8 @@ export class DataRecognizer { // find all existing savedObjects for a given type loadExistingSavedObjects(type: string) { - return this.savedObjectsClient.find({ type, perPage: 1000 }); + // TODO: define saved object type + return this.savedObjectsClient.find({ type, perPage: 1000 }); } // save the savedObjects if they do not exist already diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/rollup.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/rollup.ts index 11b0802192e1f..1e9ce3d8d5022 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/rollup.ts +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/rollup.ts @@ -5,6 +5,7 @@ */ import { SavedObject } from 'src/core/server'; +import { IndexPatternAttributes } from 'src/plugins/data/server'; import { SavedObjectsClientContract } from 'kibana/server'; import { FieldId } from '../../../../common/types/fields'; import { ES_AGGREGATION } from '../../../../common/constants/aggregation_types'; @@ -58,8 +59,8 @@ export async function rollupServiceProvider( async function loadRollupIndexPattern( indexPattern: string, savedObjectsClient: SavedObjectsClientContract -): Promise { - const resp = await savedObjectsClient.find({ +): Promise | null> { + const resp = await savedObjectsClient.find({ type: 'index-pattern', fields: ['title', 'type', 'typeMeta'], perPage: 1000, diff --git a/x-pack/legacy/plugins/siem/public/components/links/index.tsx b/x-pack/legacy/plugins/siem/public/components/links/index.tsx index 4f74f9ff2f5d6..b6548e3e950ba 100644 --- a/x-pack/legacy/plugins/siem/public/components/links/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/links/index.tsx @@ -44,7 +44,10 @@ const CaseDetailsLinkComponent: React.FC<{ children?: React.ReactNode; detailNam children, detailName, }) => ( - + {children ? children : detailName} ); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index 8eb08bd3d62f0..e1b3951a2317d 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -67,7 +67,7 @@ describe('SIEM Navigation', () => { detailName: undefined, navTabs: { case: { - disabled: true, + disabled: false, href: '#/link-to/case', id: 'case', name: 'Case', @@ -160,7 +160,7 @@ describe('SIEM Navigation', () => { filters: [], navTabs: { case: { - disabled: true, + disabled: false, href: '#/link-to/case', id: 'case', name: 'Case', diff --git a/x-pack/legacy/plugins/siem/public/containers/case/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/api.ts index 830e00c70975e..bff3bfd62a85c 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/api.ts @@ -5,12 +5,12 @@ */ import { KibanaServices } from '../../lib/kibana'; -import { AllCases, FetchCasesProps, Case, NewCase, SortFieldCase } from './types'; -import { Direction } from '../../graphql/types'; +import { FetchCasesProps, Case, NewCase, SortFieldCase, AllCases, CaseSnake } from './types'; import { throwIfNotOk } from '../../hooks/api/api'; import { CASES_URL } from './constants'; +import { convertToCamelCase, convertAllCasesToCamel } from './utils'; -export const getCase = async (caseId: string, includeComments: boolean) => { +export const getCase = async (caseId: string, includeComments: boolean): Promise => { const response = await KibanaServices.get().http.fetch(`${CASES_URL}/${caseId}`, { method: 'GET', asResponse: true, @@ -19,7 +19,7 @@ export const getCase = async (caseId: string, includeComments: boolean) => { }, }); await throwIfNotOk(response.response); - return response.body!; + return convertToCamelCase(response.body!); }; export const getCases = async ({ @@ -31,7 +31,7 @@ export const getCases = async ({ page: 1, perPage: 20, sortField: SortFieldCase.createdAt, - sortOrder: Direction.desc, + sortOrder: 'desc', }, }: FetchCasesProps): Promise => { const tags = [...(filterOptions.tags?.map(t => `case-workflow.attributes.tags: ${t}`) ?? [])]; @@ -46,7 +46,7 @@ export const getCases = async ({ asResponse: true, }); await throwIfNotOk(response.response); - return response.body!; + return convertAllCasesToCamel(response.body!); }; export const createCase = async (newCase: NewCase): Promise => { @@ -56,18 +56,19 @@ export const createCase = async (newCase: NewCase): Promise => { body: JSON.stringify(newCase), }); await throwIfNotOk(response.response); - return response.body!; + return convertToCamelCase(response.body!); }; export const updateCaseProperty = async ( caseId: string, - updatedCase: Partial + updatedCase: Partial, + version: string ): Promise> => { const response = await KibanaServices.get().http.fetch(`${CASES_URL}/${caseId}`, { method: 'PATCH', asResponse: true, - body: JSON.stringify(updatedCase), + body: JSON.stringify({ case: updatedCase, version }), }); await throwIfNotOk(response.response); - return response.body!; + return convertToCamelCase, Partial>(response.body!); }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/types.ts b/x-pack/legacy/plugins/siem/public/containers/case/types.ts index 0f80b2327a30c..1aea0b0f50a89 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/types.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Direction } from '../../graphql/types'; interface FormData { isNew?: boolean; } @@ -15,22 +14,35 @@ export interface NewCase extends FormData { title: string; } -export interface Case { +export interface CaseSnake { case_id: string; created_at: string; - created_by: ElasticUser; + created_by: ElasticUserSnake; description: string; state: string; tags: string[]; title: string; updated_at: string; + version?: string; +} + +export interface Case { + caseId: string; + createdAt: string; + createdBy: ElasticUser; + description: string; + state: string; + tags: string[]; + title: string; + updatedAt: string; + version?: string; } export interface QueryParams { page: number; perPage: number; sortField: SortFieldCase; - sortOrder: Direction; + sortOrder: 'asc' | 'desc'; } export interface FilterOptions { @@ -38,21 +50,33 @@ export interface FilterOptions { tags: string[]; } +export interface AllCasesSnake { + cases: CaseSnake[]; + page: number; + per_page: number; + total: number; +} + export interface AllCases { cases: Case[]; page: number; - per_page: number; + perPage: number; total: number; } export enum SortFieldCase { - createdAt = 'created_at', + createdAt = 'createdAt', state = 'state', - updatedAt = 'updated_at', + updatedAt = 'updatedAt', +} + +export interface ElasticUserSnake { + readonly username: string; + readonly full_name?: string | null; } export interface ElasticUser { readonly username: string; - readonly full_name?: string; + readonly fullName?: string | null; } export interface FetchCasesProps { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx index 8cc961c68fdf0..bf76b69ef22d6 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx @@ -50,16 +50,16 @@ const dataFetchReducer = (state: CaseState, action: Action): CaseState => { } }; const initialData: Case = { - case_id: '', - created_at: '', - created_by: { + caseId: '', + createdAt: '', + createdBy: { username: '', }, description: '', state: '', tags: [], title: '', - updated_at: '', + updatedAt: '', }; export const useGetCase = (caseId: string): [CaseState] => { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx index db9c07747ba04..4037823ccfc94 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_cases.tsx @@ -17,7 +17,6 @@ import { } from './constants'; import { AllCases, SortFieldCase, FilterOptions, QueryParams } from './types'; import { getTypedPayload } from './utils'; -import { Direction } from '../../graphql/types'; import { errorToToaster } from '../../components/ml/api/error_to_toaster'; import { useStateToaster } from '../../components/toasters'; import * as i18n from './translations'; @@ -31,16 +30,9 @@ export interface UseGetCasesState { filterOptions: FilterOptions; } -export interface QueryArgs { - page?: number; - perPage?: number; - sortField?: SortFieldCase; - sortOrder?: Direction; -} - export interface Action { type: string; - payload?: AllCases | QueryArgs | FilterOptions; + payload?: AllCases | Partial | FilterOptions; } const dataFetchReducer = (state: UseGetCasesState, action: Action): UseGetCasesState => { switch (action.type) { @@ -83,13 +75,13 @@ const dataFetchReducer = (state: UseGetCasesState, action: Action): UseGetCasesS const initialData: AllCases = { page: 0, - per_page: 0, + perPage: 0, total: 0, cases: [], }; export const useGetCases = (): [ UseGetCasesState, - Dispatch>, + Dispatch>>, Dispatch> ] => { const [state, dispatch] = useReducer(dataFetchReducer, { @@ -104,11 +96,11 @@ export const useGetCases = (): [ page: DEFAULT_TABLE_ACTIVE_PAGE, perPage: DEFAULT_TABLE_LIMIT, sortField: SortFieldCase.createdAt, - sortOrder: Direction.desc, + sortOrder: 'desc', }, }); - const [queryParams, setQueryParams] = useState(state.queryParams as QueryArgs); - const [filterQuery, setFilters] = useState(state.filterOptions as FilterOptions); + const [queryParams, setQueryParams] = useState>(state.queryParams); + const [filterQuery, setFilters] = useState(state.filterOptions); const [, dispatchToaster] = useStateToaster(); useEffect(() => { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx index 68592c17e58dc..62e3d87b528c0 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_update_case.tsx @@ -96,7 +96,11 @@ export const useUpdateCase = ( const updateData = async (updateKey: keyof Case) => { dispatch({ type: FETCH_INIT }); try { - const response = await updateCaseProperty(caseId, { [updateKey]: state.data[updateKey] }); + const response = await updateCaseProperty( + caseId, + { [updateKey]: state.data[updateKey] }, + state.data.version ?? '' // saved object versions are typed as string | undefined, hope that's not true + ); dispatch({ type: FETCH_SUCCESS, payload: response }); } catch (error) { errorToToaster({ title: i18n.ERROR_TITLE, error, dispatchToaster }); diff --git a/x-pack/legacy/plugins/siem/public/containers/case/utils.ts b/x-pack/legacy/plugins/siem/public/containers/case/utils.ts index 8e6eaca1a8f0c..14a3819bdfdad 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/utils.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/utils.ts @@ -4,4 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ +import { camelCase, isArray, isObject, set } from 'lodash'; +import { AllCases, AllCasesSnake, Case, CaseSnake } from './types'; + export const getTypedPayload = (a: unknown): T => a as T; + +export const convertArrayToCamelCase = (arrayOfSnakes: unknown[]): unknown[] => + arrayOfSnakes.reduce((acc: unknown[], value) => { + if (isArray(value)) { + return [...acc, convertArrayToCamelCase(value)]; + } else if (isObject(value)) { + return [...acc, convertToCamelCase(value)]; + } else { + return [...acc, value]; + } + }, []); + +export const convertToCamelCase = (snakeCase: T): U => + Object.entries(snakeCase).reduce((acc, [key, value]) => { + if (isArray(value)) { + set(acc, camelCase(key), convertArrayToCamelCase(value)); + } else if (isObject(value)) { + set(acc, camelCase(key), convertToCamelCase(value)); + } else { + set(acc, camelCase(key), value); + } + return acc; + }, {} as U); + +export const convertAllCasesToCamel = (snakeCases: AllCasesSnake): AllCases => ({ + cases: snakeCases.cases.map(snakeCase => convertToCamelCase(snakeCase)), + page: snakeCases.page, + perPage: snakeCases.per_page, + total: snakeCases.total, +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx new file mode 100644 index 0000000000000..98a67304fcf1f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx @@ -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 { SortFieldCase } from '../../../../../containers/case/types'; +import { UseGetCasesState } from '../../../../../containers/case/use_get_cases'; + +export const useGetCasesMockState: UseGetCasesState = { + data: { + cases: [ + { + caseId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:23.627Z', + createdBy: { username: 'elastic' }, + description: 'Security banana Issue', + state: 'open', + tags: ['defacement'], + title: 'Another horrible breach', + updatedAt: '2020-02-13T19:44:23.627Z', + }, + { + caseId: '362a5c10-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:13.328Z', + createdBy: { username: 'elastic' }, + description: 'Security banana Issue', + state: 'open', + tags: ['phishing'], + title: 'Bad email', + updatedAt: '2020-02-13T19:44:13.328Z', + }, + { + caseId: '34f8b9e0-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:11.328Z', + createdBy: { username: 'elastic' }, + description: 'Security banana Issue', + state: 'open', + tags: ['phishing'], + title: 'Bad email', + updatedAt: '2020-02-13T19:44:11.328Z', + }, + { + caseId: '31890e90-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:05.563Z', + createdBy: { username: 'elastic' }, + description: 'Security banana Issue', + state: 'closed', + tags: ['phishing'], + title: 'Uh oh', + updatedAt: '2020-02-18T21:32:24.056Z', + }, + { + caseId: '2f5b3210-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:01.901Z', + createdBy: { username: 'elastic' }, + description: 'Security banana Issue', + state: 'open', + tags: ['phishing'], + title: 'Uh oh', + updatedAt: '2020-02-13T19:44:01.901Z', + }, + ], + page: 1, + perPage: 5, + total: 10, + }, + isLoading: false, + isError: false, + queryParams: { + page: 1, + perPage: 5, + sortField: SortFieldCase.createdAt, + sortOrder: 'desc', + }, + filterOptions: { search: '', tags: [] }, +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx index 92cd16fd2000e..4c47bf605051d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx @@ -14,14 +14,15 @@ import * as i18n from './translations'; export type CasesColumns = EuiTableFieldDataColumnType | EuiTableComputedColumnType; -const renderStringField = (field: string) => (field != null ? field : getEmptyTagValue()); +const renderStringField = (field: string, dataTestSubj: string) => + field != null ? {field} : getEmptyTagValue(); export const getCasesColumns = (): CasesColumns[] => [ { name: i18n.CASE_TITLE, render: (theCase: Case) => { - if (theCase.case_id != null && theCase.title != null) { - return {theCase.title}; + if (theCase.caseId != null && theCase.title != null) { + return {theCase.title}; } return getEmptyTagValue(); }, @@ -34,7 +35,11 @@ export const getCasesColumns = (): CasesColumns[] => [ return ( {tags.map((tag: string, i: number) => ( - + {tag} ))} @@ -46,28 +51,39 @@ export const getCasesColumns = (): CasesColumns[] => [ truncateText: true, }, { - field: 'created_at', + field: 'createdAt', name: i18n.CREATED_AT, sortable: true, - render: (createdAt: Case['created_at']) => { + render: (createdAt: Case['createdAt']) => { if (createdAt != null) { - return ; + return ( + + ); } return getEmptyTagValue(); }, }, { - field: 'created_by.username', + field: 'createdBy.username', name: i18n.REPORTER, - render: (createdBy: Case['created_by']['username']) => renderStringField(createdBy), + render: (createdBy: Case['createdBy']['username']) => + renderStringField(createdBy, `case-table-column-username`), }, { - field: 'updated_at', + field: 'updatedAt', name: i18n.LAST_UPDATED, sortable: true, - render: (updatedAt: Case['updated_at']) => { + render: (updatedAt: Case['updatedAt']) => { if (updatedAt != null) { - return ; + return ( + + ); } return getEmptyTagValue(); }, @@ -76,6 +92,6 @@ export const getCasesColumns = (): CasesColumns[] => [ field: 'state', name: i18n.STATE, sortable: true, - render: (state: Case['state']) => renderStringField(state), + render: (state: Case['state']) => renderStringField(state, `case-table-column-state`), }, ]; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx new file mode 100644 index 0000000000000..5a87cf53142f7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx @@ -0,0 +1,97 @@ +/* + * 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 React from 'react'; +import { mount } from 'enzyme'; +import moment from 'moment-timezone'; +import { AllCases } from './'; +import { TestProviders } from '../../../../mock'; +import { useGetCasesMockState } from './__mock__'; +import * as apiHook from '../../../../containers/case/use_get_cases'; + +describe('AllCases', () => { + const setQueryParams = jest.fn(); + const setFilters = jest.fn(); + beforeEach(() => { + jest.resetAllMocks(); + jest + .spyOn(apiHook, 'useGetCases') + .mockReturnValue([useGetCasesMockState, setQueryParams, setFilters]); + moment.tz.setDefault('UTC'); + }); + it('should render AllCases', () => { + const wrapper = mount( + + + + ); + expect( + wrapper + .find(`a[data-test-subj="case-details-link"]`) + .first() + .prop('href') + ).toEqual(`#/link-to/case/${useGetCasesMockState.data.cases[0].caseId}`); + expect( + wrapper + .find(`a[data-test-subj="case-details-link"]`) + .first() + .text() + ).toEqual(useGetCasesMockState.data.cases[0].title); + expect( + wrapper + .find(`[data-test-subj="case-table-column-state"]`) + .first() + .text() + ).toEqual(useGetCasesMockState.data.cases[0].state); + expect( + wrapper + .find(`span[data-test-subj="case-table-column-tags-0"]`) + .first() + .prop('title') + ).toEqual(useGetCasesMockState.data.cases[0].tags[0]); + expect( + wrapper + .find(`[data-test-subj="case-table-column-username"]`) + .first() + .text() + ).toEqual(useGetCasesMockState.data.cases[0].createdBy.username); + expect( + wrapper + .find(`[data-test-subj="case-table-column-createdAt"]`) + .first() + .prop('value') + ).toEqual(useGetCasesMockState.data.cases[0].createdAt); + expect( + wrapper + .find(`[data-test-subj="case-table-column-updatedAt"]`) + .first() + .prop('value') + ).toEqual(useGetCasesMockState.data.cases[0].updatedAt); + + expect( + wrapper + .find(`[data-test-subj="case-table-case-count"]`) + .first() + .text() + ).toEqual('Showing 10 cases'); + }); + it('should tableHeaderSortButton AllCases', () => { + const wrapper = mount( + + + + ); + wrapper + .find('[data-test-subj="tableHeaderCell_state_5"] [data-test-subj="tableHeaderSortButton"]') + .simulate('click'); + expect(setQueryParams).toBeCalledWith({ + page: 1, + perPage: 5, + sortField: 'state', + sortOrder: 'asc', + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx index b1dd39c95e191..3253a036c2990 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx @@ -18,7 +18,6 @@ import * as i18n from './translations'; import { getCasesColumns } from './columns'; import { SortFieldCase, Case, FilterOptions } from '../../../../containers/case/types'; -import { Direction } from '../../../../graphql/types'; import { useGetCases } from '../../../../containers/case/use_get_cases'; import { EuiBasicTableOnChange } from '../../../detection_engine/rules/types'; import { Panel } from '../../../../components/panel'; @@ -32,7 +31,16 @@ import { UtilityBarText, } from '../../../../components/detection_engine/utility_bar'; import { getCreateCaseUrl } from '../../../../components/link_to'; - +const getSortField = (field: string): SortFieldCase => { + if (field === SortFieldCase.createdAt) { + return SortFieldCase.createdAt; + } else if (field === SortFieldCase.state) { + return SortFieldCase.state; + } else if (field === SortFieldCase.updatedAt) { + return SortFieldCase.updatedAt; + } + return SortFieldCase.createdAt; +}; export const AllCases = React.memo(() => { const [ { data, isLoading, queryParams, filterOptions }, @@ -44,24 +52,10 @@ export const AllCases = React.memo(() => { ({ page, sort }: EuiBasicTableOnChange) => { let newQueryParams = queryParams; if (sort) { - let newSort; - switch (sort.field) { - case 'state': - newSort = SortFieldCase.state; - break; - case 'created_at': - newSort = SortFieldCase.createdAt; - break; - case 'updated_at': - newSort = SortFieldCase.updatedAt; - break; - default: - newSort = SortFieldCase.createdAt; - } newQueryParams = { ...newQueryParams, - sortField: newSort, - sortOrder: sort.direction as Direction, + sortField: getSortField(sort.field), + sortOrder: sort.direction, }; } if (page) { @@ -114,7 +108,9 @@ export const AllCases = React.memo(() => { - {i18n.SHOWING_CASES(data.total ?? 0)} + + {i18n.SHOWING_CASES(data.total ?? 0)} + diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx new file mode 100644 index 0000000000000..7480c4fc4bb2a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CaseProps } from '../index'; +import { Case } from '../../../../../containers/case/types'; + +export const caseProps: CaseProps = { + caseId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', + initialData: { + caseId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:23.627Z', + createdBy: { fullName: null, username: 'elastic' }, + description: 'Security banana Issue', + state: 'open', + tags: ['defacement'], + title: 'Another horrible breach!!', + updatedAt: '2020-02-19T15:02:57.995Z', + }, + isLoading: false, +}; + +export const data: Case = { + caseId: '3c4ddcc0-4e99-11ea-9290-35d05cb55c15', + createdAt: '2020-02-13T19:44:23.627Z', + createdBy: { username: 'elastic', fullName: null }, + description: 'Security banana Issue', + state: 'open', + tags: ['defacement'], + title: 'Another horrible breach!!', + updatedAt: '2020-02-19T15:02:57.995Z', +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx new file mode 100644 index 0000000000000..a9e694bad705d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.test.tsx @@ -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 React from 'react'; +import { mount } from 'enzyme'; +import { CaseComponent } from './'; +import * as apiHook from '../../../../containers/case/use_update_case'; +import { caseProps, data } from './__mock__'; +import { TestProviders } from '../../../../mock'; + +describe('CaseView ', () => { + const dispatchUpdateCaseProperty = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + jest.spyOn(apiHook, 'useUpdateCase').mockReturnValue([{ data }, dispatchUpdateCaseProperty]); + }); + + it('should render CaseComponent', () => { + const wrapper = mount( + + + + ); + expect( + wrapper + .find(`[data-test-subj="case-view-title"]`) + .first() + .prop('title') + ).toEqual(data.title); + expect( + wrapper + .find(`[data-test-subj="case-view-state"]`) + .first() + .text() + ).toEqual(data.state); + expect( + wrapper + .find(`[data-test-subj="case-view-tag-list"] .euiBadge__text`) + .first() + .text() + ).toEqual(data.tags[0]); + expect( + wrapper + .find(`[data-test-subj="case-view-username"]`) + .first() + .text() + ).toEqual(data.createdBy.username); + expect( + wrapper + .find(`[data-test-subj="case-view-createdAt"]`) + .first() + .prop('value') + ).toEqual(data.createdAt); + expect( + wrapper + .find(`[data-test-subj="case-view-description"]`) + .first() + .prop('raw') + ).toEqual(data.description); + }); + + it('should dispatch update state when button is toggled', () => { + const wrapper = mount( + + + + ); + + wrapper + .find('input[data-test-subj="toggle-case-state"]') + .simulate('change', { target: { value: false } }); + + expect(dispatchUpdateCaseProperty).toBeCalledWith({ + updateKey: 'state', + updateValue: 'closed', + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index a92cf99097fce..5cd71c5855d34 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -60,13 +60,13 @@ const BackgroundWrapper = styled.div` `} `; -interface CasesProps { +export interface CaseProps { caseId: string; initialData: Case; isLoading: boolean; } -export const Cases = React.memo(({ caseId, initialData, isLoading }) => { +export const CaseComponent = React.memo(({ caseId, initialData, isLoading }) => { const [{ data }, dispatchUpdateCaseProperty] = useUpdateCase(caseId, initialData); const [isEditDescription, setIsEditDescription] = useState(false); const [isEditTags, setIsEditTags] = useState(false); @@ -162,14 +162,14 @@ export const Cases = React.memo(({ caseId, initialData, isLoading }) ]; const userActions = [ { - avatarName: data.created_by.username, + avatarName: data.createdBy.username, title: (

- {`${data.created_by.username}`} + {`${data.createdBy.username}`} {` ${i18n.ADDED_DESCRIPTION} `}{' '} - + {/* STEPH FIX come back and add label `on` */}

@@ -206,7 +206,7 @@ export const Cases = React.memo(({ caseId, initialData, isLoading })
) : ( - + ), }, ]; @@ -229,6 +229,7 @@ export const Cases = React.memo(({ caseId, initialData, isLoading }) href: getCaseUrl(), text: i18n.BACK_TO_ALL, }} + data-test-subj="case-view-title" titleNode={titleNode} title={title} > @@ -239,13 +240,21 @@ export const Cases = React.memo(({ caseId, initialData, isLoading }) {i18n.STATUS} - {data.state} + + {data.state} + {i18n.CASE_OPENED} - + @@ -255,6 +264,7 @@ export const Cases = React.memo(({ caseId, initialData, isLoading }) (({ caseId, initialData, isLoading }) - + { ); } - return ; + return ; }); CaseView.displayName = 'CaseView'; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx index 9fd1525003b0b..7d79e287b22e7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/create/index.tsx @@ -48,8 +48,8 @@ export const Create = React.memo(() => { } }, [form]); - if (newCase && newCase.case_id) { - return ; + if (newCase && newCase.caseId) { + return ; } return ( diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx index b80ee58f8abbf..33e0a9541c5b4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/user_list/index.tsx @@ -42,7 +42,7 @@ const renderUsers = (users: ElasticUser[]) => {

- {username} + {username}

@@ -50,7 +50,7 @@ const renderUsers = (users: ElasticUser[]) => {
window.alert('Email clicked')} + onClick={() => {}} // TO DO iconType="email" aria-label="email" /> diff --git a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx index 42d333f4f893e..a087dca38de00 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx @@ -55,7 +55,7 @@ export const navTabs: SiemNavTab = { id: SiemPageName.case, name: i18n.CASE, href: getCaseUrl(), - disabled: true, + disabled: false, urlKey: 'case', }, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.ts index bcf091544e52a..d1f41efdddd14 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_filter.ts @@ -51,6 +51,15 @@ interface GetFilterArgs { index: string[] | undefined | null; } +interface QueryAttributes { + // NOTE: doesn't match Query interface + query: { + query: string; + language: string; + }; + filters: PartialFilter[]; +} + export const getFilter = async ({ filters, index, @@ -72,7 +81,10 @@ export const getFilter = async ({ if (savedId != null && index != null) { try { // try to get the saved object first - const savedObject = await services.savedObjectsClient.get('query', savedId); + const savedObject = await services.savedObjectsClient.get( + 'query', + savedId + ); return getQueryFilter( savedObject.attributes.query.query, savedObject.attributes.query.language, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts index 29d4d2182bd53..c93990e25b52b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/get_input_output_index.ts @@ -16,7 +16,9 @@ export const getInputIndex = async ( if (inputIndex != null) { return inputIndex; } else { - const configuration = await services.savedObjectsClient.get('config', version); + const configuration = await services.savedObjectsClient.get<{ + 'siem:defaultIndex': string[]; + }>('config', version); if (configuration.attributes != null && configuration.attributes[DEFAULT_INDEX_KEY] != null) { return configuration.attributes[DEFAULT_INDEX_KEY]; } else { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 79337aa91b1fe..b9ac852701268 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -22,7 +22,17 @@ import { SignalRuleAlertTypeDefinition } from './types'; import { getGapBetweenRuns } from './utils'; import { ruleStatusSavedObjectType } from '../rules/saved_object_mappings'; import { IRuleSavedAttributesSavedObjectAttributes } from '../rules/types'; - +interface AlertAttributes { + enabled: boolean; + name: string; + tags: string[]; + createdBy: string; + createdAt: string; + updatedBy: string; + schedule: { + interval: string; + }; +} export const signalRulesAlertType = ({ logger, version, @@ -82,7 +92,7 @@ export const signalRulesAlertType = ({ type, } = params; // TODO: Remove this hard extraction of name once this is fixed: https://github.com/elastic/kibana/issues/50522 - const savedObject = await services.savedObjectsClient.get('alert', alertId); + const savedObject = await services.savedObjectsClient.get('alert', alertId); const ruleStatusSavedObjects = await services.savedObjectsClient.find< IRuleSavedAttributesSavedObjectAttributes >({ @@ -123,15 +133,15 @@ export const signalRulesAlertType = ({ ); } - const name: string = savedObject.attributes.name; - const tags: string[] = savedObject.attributes.tags; + const name = savedObject.attributes.name; + const tags = savedObject.attributes.tags; - const createdBy: string = savedObject.attributes.createdBy; - const createdAt: string = savedObject.attributes.createdAt; - const updatedBy: string = savedObject.attributes.updatedBy; - const updatedAt: string = savedObject.updated_at ?? ''; - const interval: string = savedObject.attributes.schedule.interval; - const enabled: boolean = savedObject.attributes.enabled; + const createdBy = savedObject.attributes.createdBy; + const createdAt = savedObject.attributes.createdAt; + const updatedBy = savedObject.attributes.updatedBy; + const updatedAt = savedObject.updated_at ?? ''; + const interval = savedObject.attributes.schedule.interval; + const enabled = savedObject.attributes.enabled; const gap = getGapBetweenRuns({ previousStartedAt: previousStartedAt != null ? moment(previousStartedAt) : null, // TODO: Remove this once previousStartedAt is no longer a string interval, diff --git a/x-pack/legacy/plugins/task_manager/server/migrations.ts b/x-pack/legacy/plugins/task_manager/server/migrations.ts index 7dce763ddf309..97c4f97f59c58 100644 --- a/x-pack/legacy/plugins/task_manager/server/migrations.ts +++ b/x-pack/legacy/plugins/task_manager/server/migrations.ts @@ -7,7 +7,7 @@ import { SavedObject } from '../../../../../src/core/server'; export const migrations = { task: { - '7.4.0': (doc: SavedObject) => ({ + '7.4.0': (doc: SavedObject>) => ({ ...doc, updated_at: new Date().toISOString(), }), @@ -18,7 +18,7 @@ export const migrations = { function moveIntervalIntoSchedule({ attributes: { interval, ...attributes }, ...doc -}: SavedObject) { +}: SavedObject>) { return { ...doc, attributes: { diff --git a/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.ts b/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.ts new file mode 100644 index 0000000000000..b55a4cd5c7bd6 --- /dev/null +++ b/x-pack/legacy/plugins/transform/public/__mocks__/shared_imports.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 function XJsonMode() {} +export function setDependencyCache() {} +export { mlInMemoryTableBasicFactory } from '../../../ml/public/application/components/ml_in_memory_table'; +export const SORT_DIRECTION = { ASC: 'asc' }; diff --git a/x-pack/legacy/plugins/transform/public/app/app.tsx b/x-pack/legacy/plugins/transform/public/app/app.tsx index 825c1761bf619..0f21afbcccca8 100644 --- a/x-pack/legacy/plugins/transform/public/app/app.tsx +++ b/x-pack/legacy/plugins/transform/public/app/app.tsx @@ -16,6 +16,7 @@ import { getAppProviders } from './app_dependencies'; import { AuthorizationContext } from './lib/authorization'; import { AppDependencies } from '../shim'; +import { CloneTransformSection } from './sections/clone_transform'; import { CreateTransformSection } from './sections/create_transform'; import { TransformManagementSection } from './sections/transform_management'; @@ -39,6 +40,10 @@ export const App: FC = () => { return (
+ { + test('isMatchAllQuery()', () => { + expect(isMatchAllQuery(defaultQuery)).toBe(false); + expect(isMatchAllQuery(matchAllQuery)).toBe(true); + expect(isMatchAllQuery(simpleQuery)).toBe(false); + }); + test('isSimpleQuery()', () => { expect(isSimpleQuery(defaultQuery)).toBe(true); expect(isSimpleQuery(matchAllQuery)).toBe(false); diff --git a/x-pack/legacy/plugins/transform/public/app/common/request.ts b/x-pack/legacy/plugins/transform/public/app/common/request.ts index 5d508f3d245d3..3b740de177ef8 100644 --- a/x-pack/legacy/plugins/transform/public/app/common/request.ts +++ b/x-pack/legacy/plugins/transform/public/app/common/request.ts @@ -53,6 +53,12 @@ export function isSimpleQuery(arg: any): arg is SimpleQuery { return arg.query_string !== undefined; } +export const matchAllQuery = { match_all: {} }; +export function isMatchAllQuery(query: any): boolean { + return query.match_all !== undefined && Object.keys(query.match_all).length === 0; +} + +export const defaultQuery: PivotQuery = { query_string: { query: '*' } }; export function isDefaultQuery(query: PivotQuery): boolean { return isSimpleQuery(query) && query.query_string.query === '*'; } diff --git a/x-pack/legacy/plugins/transform/public/app/constants/index.ts b/x-pack/legacy/plugins/transform/public/app/constants/index.ts index 85ffc222f59a2..78b5f018dd782 100644 --- a/x-pack/legacy/plugins/transform/public/app/constants/index.ts +++ b/x-pack/legacy/plugins/transform/public/app/constants/index.ts @@ -8,6 +8,7 @@ export const CLIENT_BASE_PATH = '/management/elasticsearch/transform'; export enum SECTION_SLUG { HOME = 'transform_management', + CLONE_TRANSFORM = 'clone_transform', CREATE_TRANSFORM = 'create_transform', } diff --git a/x-pack/legacy/plugins/transform/public/app/hooks/use_x_json_mode.ts b/x-pack/legacy/plugins/transform/public/app/hooks/use_x_json_mode.ts new file mode 100644 index 0000000000000..1017ce198ff29 --- /dev/null +++ b/x-pack/legacy/plugins/transform/public/app/hooks/use_x_json_mode.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. + */ +import { useState } from 'react'; +import { collapseLiteralStrings, expandLiteralStrings, XJsonMode } from '../../shared_imports'; + +export const xJsonMode = new XJsonMode(); + +export const useXJsonMode = (json: string) => { + const [xJson, setXJson] = useState(expandLiteralStrings(json)); + + return { + xJson, + setXJson, + xJsonMode, + convertToJson: collapseLiteralStrings, + }; +}; diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts index 3e55d509a94ab..aa4cd21281e22 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts @@ -4,17 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/public'; +import { SavedObjectsClientContract, SimpleSavedObject, IUiSettingsClient } from 'src/core/public'; import { IndexPattern, esQuery, IndexPatternsContract, + IndexPatternAttributes, } from '../../../../../../../../src/plugins/data/public'; +import { matchAllQuery } from '../../common'; + type IndexPatternId = string; type SavedSearchId = string; -let indexPatternCache = []; +let indexPatternCache: Array>> = []; let fullIndexPatterns; let currentIndexPattern = null; let currentSavedSearch = null; @@ -27,7 +30,7 @@ export function loadIndexPatterns( ) { fullIndexPatterns = indexPatterns; return savedObjectsClient - .find({ + .find({ type: 'index-pattern', fields: ['id', 'title', 'type', 'fields'], perPage: 10000, @@ -53,6 +56,10 @@ export function loadIndexPatterns( }); } +export function getIndexPatternIdByTitle(indexPatternTitle: string): string | undefined { + return indexPatternCache.find(d => d?.attributes?.title === indexPatternTitle)?.id; +} + type CombinedQuery = Record<'bool', any> | unknown; export function loadCurrentIndexPattern( @@ -69,12 +76,20 @@ export function loadCurrentSavedSearch(savedSearches: any, savedSearchId: SavedS return currentSavedSearch; } +function isIndexPattern(arg: any): arg is IndexPattern { + return arg !== undefined; +} // Helper for creating the items used for searching and job creation. export function createSearchItems( indexPattern: IndexPattern | undefined, savedSearch: any, config: IUiSettingsClient -) { +): { + indexPattern: IndexPattern; + savedSearch: any; + query: any; + combinedQuery: CombinedQuery; +} { // query is only used by the data visualizer as it needs // a lucene query_string. // Using a blank query will cause match_all:{} to be used @@ -86,17 +101,13 @@ export function createSearchItems( let combinedQuery: CombinedQuery = { bool: { - must: [ - { - match_all: {}, - }, - ], + must: [matchAllQuery], }, }; - if (indexPattern === undefined && savedSearch !== null && savedSearch.id !== undefined) { + if (!isIndexPattern(indexPattern) && savedSearch !== null && savedSearch.id !== undefined) { const searchSource = savedSearch.searchSource; - indexPattern = searchSource.getField('index'); + indexPattern = searchSource.getField('index') as IndexPattern; query = searchSource.getField('query'); const fs = searchSource.getField('filter'); @@ -107,6 +118,10 @@ export function createSearchItems( combinedQuery = esQuery.buildEsQuery(indexPattern, [query], filters, esQueryConfigs); } + if (!isIndexPattern(indexPattern)) { + throw new Error('Index Pattern is not defined.'); + } + return { indexPattern, savedSearch, diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts b/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts index 82d5362e21c02..62107cb37ff2c 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export { getIndexPatternIdByTitle, loadIndexPatterns } from './common'; export { useKibanaContext, InitializedKibanaContextValue, diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx index 5b7702a0193ec..b0a0371d2de86 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_context.tsx @@ -6,30 +6,26 @@ import React, { createContext, useContext, FC } from 'react'; +import { IUiSettingsClient } from 'kibana/public'; + import { SavedSearch } from '../../../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/types'; import { IndexPattern, IndexPatternsContract, } from '../../../../../../../../src/plugins/data/public'; -import { KibanaConfig } from '../../../../../../../../src/legacy/server/kbn_server'; - -// set() method is missing in original d.ts -interface KibanaConfigTypeFix extends KibanaConfig { - set(key: string, value: any): void; -} interface UninitializedKibanaContextValue { - initialized: boolean; + initialized: false; } export interface InitializedKibanaContextValue { combinedQuery: any; - currentIndexPattern: IndexPattern; - currentSavedSearch: SavedSearch; indexPatterns: IndexPatternsContract; - initialized: boolean; + initialized: true; kbnBaseUrl: string; - kibanaConfig: KibanaConfigTypeFix; + kibanaConfig: IUiSettingsClient; + currentIndexPattern: IndexPattern; + currentSavedSearch?: SavedSearch; } export type KibanaContextValue = UninitializedKibanaContextValue | InitializedKibanaContextValue; diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx index 0a9de49168ad4..d2cf5f2b32910 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx @@ -17,7 +17,7 @@ import { loadCurrentSavedSearch, } from './common'; -import { KibanaContext, KibanaContextValue } from './kibana_context'; +import { InitializedKibanaContextValue, KibanaContext, KibanaContextValue } from './kibana_context'; const indexPatterns = npStart.plugins.data.indexPatterns; const savedObjectsClient = npStart.core.savedObjects.client; @@ -52,20 +52,20 @@ export const KibanaProvider: FC = ({ savedObjectId, children }) => { const kibanaConfig = npStart.core.uiSettings; - const { indexPattern, savedSearch, combinedQuery } = createSearchItems( - fetchedIndexPattern, - fetchedSavedSearch, - kibanaConfig - ); - - const kibanaContext = { + const { + indexPattern: currentIndexPattern, + savedSearch: currentSavedSearch, combinedQuery, - currentIndexPattern: indexPattern, - currentSavedSearch: savedSearch, + } = createSearchItems(fetchedIndexPattern, fetchedSavedSearch, kibanaConfig); + + const kibanaContext: InitializedKibanaContextValue = { indexPatterns, initialized: true, kbnBaseUrl: npStart.core.injectedMetadata.getBasePath(), kibanaConfig, + combinedQuery, + currentIndexPattern, + currentSavedSearch, }; setContextValue(kibanaContext); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx new file mode 100644 index 0000000000000..de96a4de32962 --- /dev/null +++ b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/clone_transform_section.tsx @@ -0,0 +1,194 @@ +/* + * 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 React, { useEffect, useState, FC } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import { + EuiBetaBadge, + EuiButtonEmpty, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiPageContent, + EuiPageContentBody, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; + +import { npStart } from 'ui/new_platform'; + +import { useApi } from '../../hooks/use_api'; + +import { APP_CREATE_TRANSFORM_CLUSTER_PRIVILEGES } from '../../../../common/constants'; +import { TransformPivotConfig } from '../../common'; +import { breadcrumbService, docTitleService, BREADCRUMB_SECTION } from '../../services/navigation'; +import { documentationLinksService } from '../../services/documentation'; +import { PrivilegesWrapper } from '../../lib/authorization'; +import { + getIndexPatternIdByTitle, + loadIndexPatterns, + KibanaProvider, + RenderOnlyWithInitializedKibanaContext, +} from '../../lib/kibana'; + +import { Wizard } from '../create_transform/components/wizard'; + +const indexPatterns = npStart.plugins.data.indexPatterns; +const savedObjectsClient = npStart.core.savedObjects.client; + +interface GetTransformsResponseOk { + count: number; + transforms: TransformPivotConfig[]; +} + +interface GetTransformsResponseError { + error: { + msg: string; + path: string; + query: any; + statusCode: number; + response: string; + }; +} + +function isGetTransformsResponseError(arg: any): arg is GetTransformsResponseError { + return arg.error !== undefined; +} + +type GetTransformsResponse = GetTransformsResponseOk | GetTransformsResponseError; + +type Props = RouteComponentProps<{ transformId: string }>; +export const CloneTransformSection: FC = ({ match }) => { + // Set breadcrumb and page title + useEffect(() => { + breadcrumbService.setBreadcrumbs(BREADCRUMB_SECTION.CLONE_TRANSFORM); + docTitleService.setTitle('createTransform'); + }, []); + + const api = useApi(); + + const transformId = match.params.transformId; + + const [transformConfig, setTransformConfig] = useState(); + const [errorMessage, setErrorMessage] = useState(); + const [isInitialized, setIsInitialized] = useState(false); + const [savedObjectId, setSavedObjectId] = useState(undefined); + + const fetchTransformConfig = async () => { + try { + const transformConfigs: GetTransformsResponse = await api.getTransforms(transformId); + if (isGetTransformsResponseError(transformConfigs)) { + setTransformConfig(undefined); + setErrorMessage(transformConfigs.error.msg); + setIsInitialized(true); + return; + } + + await loadIndexPatterns(savedObjectsClient, indexPatterns); + const indexPatternTitle = Array.isArray(transformConfigs.transforms[0].source.index) + ? transformConfigs.transforms[0].source.index.join(',') + : transformConfigs.transforms[0].source.index; + const indexPatternId = getIndexPatternIdByTitle(indexPatternTitle); + + if (indexPatternId === undefined) { + throw new Error( + i18n.translate('xpack.transform.clone.errorPromptText', { + defaultMessage: 'Could not fetch the Kibana index pattern ID.', + }) + ); + } + + setSavedObjectId(indexPatternId); + + setTransformConfig(transformConfigs.transforms[0]); + setErrorMessage(undefined); + setIsInitialized(true); + } catch (e) { + setTransformConfig(undefined); + if (e.message !== undefined) { + setErrorMessage(e.message); + } else { + setErrorMessage(JSON.stringify(e, null, 2)); + } + setIsInitialized(true); + } + }; + + useEffect(() => { + fetchTransformConfig(); + // The effect should only be called once. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + + + +

+ +   + +

+
+ + + + + +
+
+ + + {typeof errorMessage !== 'undefined' && ( + +
{JSON.stringify(errorMessage)}
+
+ )} + {savedObjectId !== undefined && isInitialized === true && transformConfig !== undefined && ( + + + + + + )} +
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/index.ts b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/index.ts new file mode 100644 index 0000000000000..fef33d50130a7 --- /dev/null +++ b/x-pack/legacy/plugins/transform/public/app/sections/clone_transform/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 { CloneTransformSection } from './clone_transform_section'; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx index f326199271592..d7f1d9d099cc3 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.test.tsx @@ -20,6 +20,8 @@ jest.mock('react', () => { return { ...r, memo: (x: any) => x }; }); +jest.mock('../../../../../shared_imports'); + describe('Transform: ', () => { test('Minimal initialization', () => { const props = { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts index 3fcc3cc15803b..e5c6783db1022 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts @@ -17,6 +17,7 @@ import { getDefaultSelectableFields, getFlattenedFields, isDefaultQuery, + matchAllQuery, EsDoc, EsDocSource, EsFieldName, @@ -75,7 +76,7 @@ export const useSourceIndexData = ( index: indexPattern.title, size: SEARCH_SIZE, // Instead of using the default query (`*`), fall back to a more efficient `match_all` query. - body: { query: isDefaultQuery(query) ? { match_all: {} } : query }, + body: { query: isDefaultQuery(query) ? matchAllQuery : query }, }); if (isErrorResponse(resp)) { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx index 055f0613e4e44..a0c91c070844b 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.test.tsx @@ -19,6 +19,8 @@ jest.mock('react', () => { return { ...r, memo: (x: any) => x }; }); +jest.mock('../../../../../shared_imports'); + describe('Transform: ', () => { test('Minimal initialization', () => { const props = { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts index 7c5b60715961b..881e8c6b26658 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/index.ts @@ -5,8 +5,9 @@ */ export { + applyTransformConfigToDefineState, + getDefaultStepDefineState, StepDefineExposedState, StepDefineForm, - getDefaultStepDefineState, } from './step_define_form'; export { StepDefineSummary } from './step_define_summary'; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx index 4ff1190415dba..a2aa056c1634d 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.test.tsx @@ -27,6 +27,8 @@ jest.mock('react', () => { return { ...r, memo: (x: any) => x }; }); +jest.mock('../../../../../shared_imports'); + describe('Transform: ', () => { test('Minimal initialization', () => { const groupBy: PivotGroupByConfig = { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx index 48df371e87664..0311b26304c30 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx @@ -25,6 +25,8 @@ jest.mock('react', () => { return { ...r, memo: (x: any) => x }; }); +jest.mock('../../../../../shared_imports'); + describe('Transform: ', () => { test('Minimal initialization', () => { // Using a wrapping
element because shallow() would fail diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index b8f63ef697e78..e806d2372769e 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { isEqual } from 'lodash'; import React, { Fragment, FC, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; @@ -27,7 +28,9 @@ import { EuiSwitch, } from '@elastic/eui'; -import { dictionaryToArray } from '../../../../../../common/types/common'; +import { TransformPivotConfig } from '../../../../common'; +import { useXJsonMode, xJsonMode } from '../../../../hooks/use_x_json_mode'; +import { dictionaryToArray, Dictionary } from '../../../../../../common/types/common'; import { DropDown } from '../aggregation_dropdown'; import { AggListForm } from '../aggregation_list'; import { GroupByListForm } from '../group_by_list'; @@ -43,10 +46,12 @@ import { } from '../../../../lib/kibana'; import { - AggName, - DropDownLabel, getPivotQuery, getPreviewRequestBody, + isMatchAllQuery, + matchAllQuery, + AggName, + DropDownLabel, PivotAggDict, PivotAggsConfig, PivotAggsConfigDict, @@ -55,6 +60,7 @@ import { PivotGroupByConfigDict, PivotSupportedGroupByAggs, PIVOT_SUPPORTED_AGGS, + PIVOT_SUPPORTED_GROUP_BY_AGGS, } from '../../../../common'; import { getPivotDropdownOptions } from './common'; @@ -89,6 +95,58 @@ export function getDefaultStepDefineState( valid: false, }; } + +export function applyTransformConfigToDefineState( + state: StepDefineExposedState, + transformConfig?: TransformPivotConfig +): StepDefineExposedState { + // apply the transform configuration to wizard DEFINE state + if (transformConfig !== undefined) { + // transform aggregations config to wizard state + state.aggList = Object.keys(transformConfig.pivot.aggregations).reduce((aggList, aggName) => { + const aggConfig = transformConfig.pivot.aggregations[aggName] as Dictionary; + const agg = Object.keys(aggConfig)[0]; + aggList[aggName] = { + ...aggConfig[agg], + agg: agg as PIVOT_SUPPORTED_AGGS, + aggName, + dropDownName: aggName, + } as PivotAggsConfig; + return aggList; + }, {} as PivotAggsConfigDict); + + // transform group by config to wizard state + state.groupByList = Object.keys(transformConfig.pivot.group_by).reduce( + (groupByList, groupByName) => { + const groupByConfig = transformConfig.pivot.group_by[groupByName] as Dictionary; + const groupBy = Object.keys(groupByConfig)[0]; + groupByList[groupByName] = { + agg: groupBy as PIVOT_SUPPORTED_GROUP_BY_AGGS, + aggName: groupByName, + dropDownName: groupByName, + ...groupByConfig[groupBy], + } as PivotGroupByConfig; + return groupByList; + }, + {} as PivotGroupByConfigDict + ); + + // only apply the query from the transform config to wizard state if it's not the default query + const query = transformConfig.source.query; + if (query !== undefined && !isEqual(query, matchAllQuery)) { + state.isAdvancedSourceEditorEnabled = true; + state.searchString = ''; + state.searchQuery = query; + state.sourceConfigUpdated = true; + } + + // applying a transform config to wizard state will always result in a valid configuration + state.valid = true; + } + + return state; +} + export function isAggNameConflict( aggName: AggName, aggList: PivotAggsConfigDict, @@ -208,10 +266,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange const searchHandler = (d: Record) => { const { filterQuery, queryString } = d; const newSearch = queryString === emptySearch ? defaultSearch : queryString; - const newSearchQuery = - filterQuery.match_all && Object.keys(filterQuery.match_all).length === 0 - ? defaultSearch - : filterQuery; + const newSearchQuery = isMatchAllQuery(filterQuery) ? defaultSearch : filterQuery; setSearchString(newSearch); setSearchQuery(newSearchQuery); }; @@ -329,7 +384,13 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange const [advancedEditorConfigLastApplied, setAdvancedEditorConfigLastApplied] = useState( stringifiedPivotConfig ); - const [advancedEditorConfig, setAdvancedEditorConfig] = useState(stringifiedPivotConfig); + + const { + convertToJson, + setXJson: setAdvancedEditorConfig, + xJson: advancedEditorConfig, + } = useXJsonMode(stringifiedPivotConfig); + // source config const stringifiedSourceConfig = JSON.stringify(previewRequest.source.query, null, 2); const [ @@ -353,7 +414,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange }; const applyAdvancedPivotEditorChanges = () => { - const pivotConfig = JSON.parse(advancedEditorConfig); + const pivotConfig = JSON.parse(convertToJson(advancedEditorConfig)); const newGroupByList: PivotGroupByConfigDict = {}; if (pivotConfig !== undefined && pivotConfig.group_by !== undefined) { @@ -363,10 +424,10 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange const aggConfigKeys = Object.keys(aggConfig); const agg = aggConfigKeys[0] as PivotSupportedGroupByAggs; newGroupByList[aggName] = { + ...aggConfig[agg], agg, aggName, dropDownName: '', - ...aggConfig[agg], }; }); } @@ -380,18 +441,16 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange const aggConfigKeys = Object.keys(aggConfig); const agg = aggConfigKeys[0] as PIVOT_SUPPORTED_AGGS; newAggList[aggName] = { + ...aggConfig[agg], agg, aggName, dropDownName: '', - ...aggConfig[agg], }; }); } setAggList(newAggList); - const prettyPivotConfig = JSON.stringify(pivotConfig, null, 2); - setAdvancedEditorConfig(prettyPivotConfig); - setAdvancedEditorConfigLastApplied(prettyPivotConfig); + setAdvancedEditorConfigLastApplied(advancedEditorConfig); setAdvancedPivotEditorApplyButtonEnabled(false); }; @@ -459,13 +518,11 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange pivotAggsArr ); - const stringifiedPivotConfigUpdate = JSON.stringify(previewRequestUpdate.pivot, null, 2); const stringifiedSourceConfigUpdate = JSON.stringify( previewRequestUpdate.source.query, null, 2 ); - setAdvancedEditorConfig(stringifiedPivotConfigUpdate); setAdvancedEditorSourceConfig(stringifiedSourceConfigUpdate); onChange({ @@ -730,7 +787,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange > { @@ -745,7 +802,7 @@ export const StepDefineForm: FC = React.memo(({ overrides = {}, onChange // Try to parse the string passed on from the editor. // If parsing fails, the "Apply"-Button will be disabled try { - JSON.parse(d); + JSON.parse(convertToJson(d)); setAdvancedPivotEditorApplyButtonEnabled(true); } catch (e) { setAdvancedPivotEditorApplyButtonEnabled(false); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx index 2d9895e8ddcf1..aae366e6008d5 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.test.tsx @@ -26,6 +26,8 @@ jest.mock('react', () => { return { ...r, memo: (x: any) => x }; }); +jest.mock('../../../../../shared_imports'); + describe('Transform: ', () => { test('Minimal initialization', () => { const groupBy: PivotGroupByConfig = { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts index e454ea32d76ed..5cbdf4500e3c3 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/index.ts @@ -4,5 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export { StepDetailsForm, getDefaultStepDetailsState } from './step_details_form'; +export { + applyTransformConfigToDetailsState, + getDefaultStepDetailsState, + StepDetailsForm, +} from './step_details_form'; export { StepDetailsSummary } from './step_details_summary'; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index a01481fde343c..220923f88ed36 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -49,6 +49,22 @@ export function getDefaultStepDetailsState(): StepDetailsExposedState { }; } +export function applyTransformConfigToDetailsState( + state: StepDetailsExposedState, + transformConfig?: TransformPivotConfig +): StepDetailsExposedState { + // apply the transform configuration to wizard DETAILS state + if (transformConfig !== undefined) { + const time = transformConfig.sync?.time; + if (time !== undefined) { + state.continuousModeDateField = time.field; + state.continuousModeDelay = time.delay; + state.isContinuousModeEnabled = true; + } + } + return state; +} + interface Props { overrides?: StepDetailsExposedState; onChange(s: StepDetailsExposedState): void; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx index 109cf81da6caa..f1861755d9742 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx @@ -12,16 +12,22 @@ import { EuiSteps, EuiStepStatus } from '@elastic/eui'; import { useKibanaContext } from '../../../../lib/kibana'; -import { getCreateRequestBody } from '../../../../common'; +import { getCreateRequestBody, TransformPivotConfig } from '../../../../common'; import { + applyTransformConfigToDefineState, + getDefaultStepDefineState, StepDefineExposedState, StepDefineForm, StepDefineSummary, - getDefaultStepDefineState, } from '../step_define'; import { getDefaultStepCreateState, StepCreateForm, StepCreateSummary } from '../step_create'; -import { getDefaultStepDetailsState, StepDetailsForm, StepDetailsSummary } from '../step_details'; +import { + applyTransformConfigToDetailsState, + getDefaultStepDetailsState, + StepDetailsForm, + StepDetailsSummary, +} from '../step_details'; import { WizardNav } from '../wizard_nav'; enum KBN_MANAGEMENT_PAGE_CLASSNAME { @@ -67,17 +73,25 @@ const StepDefine: FC = ({ ); }; -export const Wizard: FC = React.memo(() => { +interface WizardProps { + cloneConfig?: TransformPivotConfig; +} + +export const Wizard: FC = React.memo(({ cloneConfig }) => { const kibanaContext = useKibanaContext(); // The current WIZARD_STEP const [currentStep, setCurrentStep] = useState(WIZARD_STEPS.DEFINE); // The DEFINE state - const [stepDefineState, setStepDefineState] = useState(getDefaultStepDefineState(kibanaContext)); + const [stepDefineState, setStepDefineState] = useState( + applyTransformConfigToDefineState(getDefaultStepDefineState(kibanaContext), cloneConfig) + ); // The DETAILS state - const [stepDetailsState, setStepDetailsState] = useState(getDefaultStepDetailsState()); + const [stepDetailsState, setStepDetailsState] = useState( + applyTransformConfigToDetailsState(getDefaultStepDetailsState(), cloneConfig) + ); const stepDetails = currentStep === WIZARD_STEPS.DETAILS ? ( diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx index 673e60de54572..288630333615a 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/create_transform_button/create_transform_button.test.tsx @@ -11,6 +11,8 @@ import { CreateTransformButton } from './create_transform_button'; jest.mock('ui/new_platform'); +jest.mock('../../../../../shared_imports'); + describe('Transform: Transform List ', () => { test('Minimal initialization', () => { const wrapper = shallow(); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx new file mode 100644 index 0000000000000..40098ac7ef72a --- /dev/null +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_clone.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useContext } from 'react'; +import { useHistory } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; + +import { + createCapabilityFailureMessage, + AuthorizationContext, +} from '../../../../lib/authorization'; + +import { CLIENT_BASE_PATH, SECTION_SLUG } from '../../../../constants'; + +interface CloneActionProps { + itemId: string; +} + +export const CloneAction: FC = ({ itemId }) => { + const history = useHistory(); + + const { canCreateTransform } = useContext(AuthorizationContext).capabilities; + + const buttonCloneText = i18n.translate('xpack.transform.transformList.cloneActionName', { + defaultMessage: 'Clone', + }); + + function clickHandler() { + history.push(`${CLIENT_BASE_PATH}/${SECTION_SLUG.CLONE_TRANSFORM}/${itemId}`); + } + + const cloneButton = ( + + {buttonCloneText} + + ); + + if (!canCreateTransform) { + const content = createCapabilityFailureMessage('canStartStopTransform'); + + return ( + + {cloneButton} + + ); + } + + return <>{cloneButton}; +}; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx index 979da13b1f83a..4795a2eb7d7bc 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_delete.test.tsx @@ -17,6 +17,8 @@ import transformListRow from '../../../../common/__mocks__/transform_list_row.js jest.mock('ui/new_platform'); +jest.mock('../../../../../shared_imports'); + describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { const Providers = getAppProviders(createPublicShim()); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx index 71a2eff39506d..5f4d4a71c71eb 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_start.test.tsx @@ -17,6 +17,8 @@ import transformListRow from '../../../../common/__mocks__/transform_list_row.js jest.mock('ui/new_platform'); +jest.mock('../../../../../shared_imports'); + describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { const Providers = getAppProviders(createPublicShim()); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx index c3b67f7661a1a..f6bb1c8b60667 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/action_stop.test.tsx @@ -17,6 +17,8 @@ import transformListRow from '../../../../common/__mocks__/transform_list_row.js jest.mock('ui/new_platform'); +jest.mock('../../../../../shared_imports'); + describe('Transform: Transform List Actions ', () => { test('Minimal initialization', () => { const Providers = getAppProviders(createPublicShim()); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx index 3d847890b2bd5..12e1ba5528c43 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.test.tsx @@ -8,13 +8,16 @@ import { getActions } from './actions'; jest.mock('ui/new_platform'); +jest.mock('../../../../../shared_imports'); + describe('Transform: Transform List Actions', () => { test('getActions()', () => { const actions = getActions({ forceDisable: false }); - expect(actions).toHaveLength(2); + expect(actions).toHaveLength(3); expect(actions[0].isPrimary).toBeTruthy(); expect(typeof actions[0].render).toBe('function'); expect(typeof actions[1].render).toBe('function'); + expect(typeof actions[2].render).toBe('function'); }); }); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx index 1773405e36e39..3e3829973e328 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/actions.tsx @@ -6,6 +6,7 @@ import React from 'react'; import { TransformListRow, TRANSFORM_STATE } from '../../../../common'; +import { CloneAction } from './action_clone'; import { StartAction } from './action_start'; import { StopAction } from './action_stop'; import { DeleteAction } from './action_delete'; @@ -21,6 +22,11 @@ export const getActions = ({ forceDisable }: { forceDisable: boolean }) => { return ; }, }, + { + render: (item: TransformListRow) => { + return ; + }, + }, { render: (item: TransformListRow) => { return ; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx index f16130bfe618b..42f04ed101ad6 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/columns.test.tsx @@ -8,6 +8,8 @@ import { getColumns } from './columns'; jest.mock('ui/new_platform'); +jest.mock('../../../../../shared_imports'); + describe('Transform: Job List Columns', () => { test('getColumns()', () => { const columns = getColumns([], () => {}, []); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx index 4f992707cbf1a..7fcaf5e6048f6 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.test.tsx @@ -13,6 +13,8 @@ import { ExpandedRow } from './expanded_row'; import transformListRow from '../../../../common/__mocks__/transform_list_row.json'; +jest.mock('../../../../../shared_imports'); + describe('Transform: Transform List ', () => { // Set timezone to US/Eastern for consistent test results. beforeEach(() => { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx index 303de6b86ac53..e1a19ddd3c742 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/transform_list.test.tsx @@ -12,6 +12,8 @@ import { TransformList } from './transform_list'; jest.mock('ui/new_platform'); +jest.mock('../../../../../shared_imports'); + describe('Transform: Transform List ', () => { test('Minimal initialization', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx index c94f5c1d57d49..f68670f0b38b2 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/transform_management_section.test.tsx @@ -14,6 +14,8 @@ jest.mock('ui/timefilter', () => { return {}; }); +jest.mock('../../../shared_imports'); + describe('Transform: ', () => { test('Minimal initialization', () => { const wrapper = shallow(); diff --git a/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts b/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts index 0e0b174f28f99..5a2f698b35154 100644 --- a/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts +++ b/x-pack/legacy/plugins/transform/public/app/services/navigation/breadcrumb.ts @@ -10,6 +10,7 @@ import { linkToHome } from './links'; export enum BREADCRUMB_SECTION { MANAGEMENT = 'management', HOME = 'home', + CLONE_TRANSFORM = 'cloneTransform', CREATE_TRANSFORM = 'createTransform', } @@ -27,6 +28,7 @@ class BreadcrumbService { private breadcrumbs: Breadcrumbs = { management: [], home: [], + cloneTransform: [], createTransform: [], }; @@ -42,6 +44,12 @@ class BreadcrumbService { href: linkToHome(), }, ]; + this.breadcrumbs.cloneTransform = [ + ...this.breadcrumbs.home, + { + text: textService.breadcrumbs.cloneTransform, + }, + ]; this.breadcrumbs.createTransform = [ ...this.breadcrumbs.home, { diff --git a/x-pack/legacy/plugins/transform/public/app/services/text/text.ts b/x-pack/legacy/plugins/transform/public/app/services/text/text.ts index df1b07e171c62..af4aea7e8db4e 100644 --- a/x-pack/legacy/plugins/transform/public/app/services/text/text.ts +++ b/x-pack/legacy/plugins/transform/public/app/services/text/text.ts @@ -14,6 +14,9 @@ class TextService { home: i18n.translate('xpack.transform.home.breadcrumbTitle', { defaultMessage: 'Transforms', }), + cloneTransform: i18n.translate('xpack.transform.cloneTransform.breadcrumbTitle', { + defaultMessage: 'Clone transform', + }), createTransform: i18n.translate('xpack.transform.createTransform.breadcrumbTitle', { defaultMessage: 'Create transform', }), diff --git a/x-pack/legacy/plugins/transform/public/shared_imports.ts b/x-pack/legacy/plugins/transform/public/shared_imports.ts index 74e0c9a3878db..248eb00c67dff 100644 --- a/x-pack/legacy/plugins/transform/public/shared_imports.ts +++ b/x-pack/legacy/plugins/transform/public/shared_imports.ts @@ -4,6 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +export { XJsonMode } from '../../../../plugins/es_ui_shared/console_lang/ace/modes/x_json'; +export { + collapseLiteralStrings, + expandLiteralStrings, +} from '../../../../../src/plugins/es_ui_shared/console_lang/lib'; + export { SendRequestConfig, SendRequestResponse, diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts index 0997647d59d23..7b4d84729b2b6 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts @@ -24,7 +24,12 @@ async function getSavedObjectAttributesFromRepo( docID: string ) { try { - return (await savedObjectsRepository.get(docType, docID)).attributes; + return ( + await savedObjectsRepository.get( + docType, + docID + ) + ).attributes; } catch (e) { return null; } diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 7ac8ea24b5218..a06048953b62c 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -10,7 +10,7 @@ import { SavedObjectsClientContract, SavedObjectAttributes, SavedObject, -} from '../../../../src/core/server'; +} from 'src/core/server'; import { ActionTypeRegistry } from './action_type_registry'; import { validateConfig, validateSecrets } from './lib'; @@ -118,7 +118,7 @@ export class ActionsClient { * Update action */ public async update({ id, action }: UpdateOptions): Promise { - const existingObject = await this.savedObjectsClient.get('action', id); + const existingObject = await this.savedObjectsClient.get('action', id); const { actionTypeId } = existingObject.attributes; const { name, config, secrets } = action; const actionType = this.actionTypeRegistry.get(actionTypeId); @@ -144,13 +144,13 @@ export class ActionsClient { * Get an action */ public async get({ id }: { id: string }): Promise { - const result = await this.savedObjectsClient.get('action', id); + const result = await this.savedObjectsClient.get('action', id); return { id, - actionTypeId: result.attributes.actionTypeId as string, - name: result.attributes.name as string, - config: result.attributes.config as Record, + actionTypeId: result.attributes.actionTypeId, + name: result.attributes.name, + config: result.attributes.config, }; } diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index c2e8795b5f133..5316e833f33d9 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -6,7 +6,7 @@ import { SavedObjectsClientContract } from '../../../../src/core/server'; import { TaskManagerStartContract } from '../../task_manager/server'; -import { GetBasePathFunction } from './types'; +import { GetBasePathFunction, RawAction } from './types'; interface CreateExecuteFunctionOptions { taskManager: TaskManagerStartContract; @@ -59,7 +59,7 @@ export function createExecuteFunction({ }; const savedObjectsClient = getScopedSavedObjectsClient(fakeRequest); - const actionSavedObject = await savedObjectsClient.get('action', id); + const actionSavedObject = await savedObjectsClient.get('action', id); const actionTaskParamsRecord = await savedObjectsClient.create('action_task_params', { actionId: id, params, diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client.ts index ad308f819da34..9a56781aa1d7d 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client.ts @@ -203,7 +203,7 @@ export class AlertsClient { } public async get({ id }: { id: string }): Promise { - const result = await this.savedObjectsClient.get('alert', id); + const result = await this.savedObjectsClient.get('alert', id); return this.getAlertFromRaw(result.id, result.attributes, result.updated_at, result.references); } diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 34278ee62dbc4..c2a3fbcf38069 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -5,8 +5,7 @@ */ import { pick, mapValues, omit } from 'lodash'; -import { Logger } from '../../../../../src/core/server'; -import { SavedObject } from '../../../../../src/core/server'; +import { Logger, SavedObject } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; import { createExecutionHandler } from './create_execution_handler'; diff --git a/x-pack/plugins/canvas/server/routes/workpad/update.ts b/x-pack/plugins/canvas/server/routes/workpad/update.ts index 0d4809a9ee168..83b8fef48e9be 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/update.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/update.ts @@ -6,8 +6,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { omit } from 'lodash'; -import { KibanaResponseFactory } from 'src/core/server'; -import { SavedObjectsClientContract } from 'src/core/server'; +import { KibanaResponseFactory, SavedObjectsClientContract } from 'src/core/server'; import { RouteInitializerDeps } from '../'; import { CANVAS_TYPE, diff --git a/x-pack/plugins/case/server/config.ts b/x-pack/plugins/case/server/config.ts index a7cb117198f9b..8ff9a793b17dc 100644 --- a/x-pack/plugins/case/server/config.ts +++ b/x-pack/plugins/case/server/config.ts @@ -7,9 +7,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const ConfigSchema = schema.object({ - enabled: schema.boolean({ defaultValue: false }), - indexPattern: schema.string({ defaultValue: '.case-test-2' }), - secret: schema.string({ defaultValue: 'Cool secret huh?' }), + enabled: schema.boolean({ defaultValue: true }), }); export type ConfigType = TypeOf; diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts index 37d087433a2ed..5ca640f0b25c3 100644 --- a/x-pack/plugins/case/server/plugin.ts +++ b/x-pack/plugins/case/server/plugin.ts @@ -34,6 +34,7 @@ export class CasePlugin { if (!config.enabled) { return; } + const service = new CaseService(this.log); this.log.debug( diff --git a/x-pack/plugins/case/server/routes/api/__tests__/update_case.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/update_case.test.ts index 23283d7f8a5be..25d5cafb4bb06 100644 --- a/x-pack/plugins/case/server/routes/api/__tests__/update_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/__tests__/update_case.test.ts @@ -27,7 +27,8 @@ describe('UPDATE case', () => { id: 'mock-id-1', }, body: { - state: 'closed', + case: { state: 'closed' }, + version: 'WzAsMV0=', }, }); @@ -38,6 +39,42 @@ describe('UPDATE case', () => { expect(typeof response.payload.updated_at).toBe('string'); expect(response.payload.state).toEqual('closed'); }); + it(`Fails with 409 if version does not match`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'patch', + params: { + id: 'mock-id-1', + }, + body: { + case: { state: 'closed' }, + version: 'badv=', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(409); + }); + it(`Fails with 406 if updated field is unchanged`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'patch', + params: { + id: 'mock-id-1', + }, + body: { + case: { state: 'open' }, + version: 'WzAsMV0=', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(406); + }); it(`Returns an error if updateCase throws`, async () => { const request = httpServerMock.createKibanaRequest({ path: '/api/cases/{id}', diff --git a/x-pack/plugins/case/server/routes/api/get_all_cases.ts b/x-pack/plugins/case/server/routes/api/get_all_cases.ts index 09075a32ac377..ba26a07dc2394 100644 --- a/x-pack/plugins/case/server/routes/api/get_all_cases.ts +++ b/x-pack/plugins/case/server/routes/api/get_all_cases.ts @@ -6,7 +6,7 @@ import { schema } from '@kbn/config-schema'; import { RouteDeps } from '.'; -import { formatAllCases, wrapError } from './utils'; +import { formatAllCases, sortToSnake, wrapError } from './utils'; import { SavedObjectsFindOptionsSchema } from './schema'; import { AllCases } from './types'; @@ -23,7 +23,10 @@ export function initGetAllCasesApi({ caseService, router }: RouteDeps) { const args = request.query ? { client: context.core.savedObjects.client, - options: request.query, + options: { + ...request.query, + sortField: sortToSnake(request.query.sortField ?? ''), + }, } : { client: context.core.savedObjects.client, diff --git a/x-pack/plugins/case/server/routes/api/schema.ts b/x-pack/plugins/case/server/routes/api/schema.ts index 962dc474254f0..468abc8e7226f 100644 --- a/x-pack/plugins/case/server/routes/api/schema.ts +++ b/x-pack/plugins/case/server/routes/api/schema.ts @@ -41,6 +41,11 @@ export const UpdatedCaseSchema = schema.object({ title: schema.maybe(schema.string()), }); +export const UpdateCaseArguments = schema.object({ + case: UpdatedCaseSchema, + version: schema.string(), +}); + export const SavedObjectsFindOptionsSchema = schema.object({ defaultSearchOperator: schema.maybe(schema.oneOf([schema.literal('AND'), schema.literal('OR')])), fields: schema.maybe(schema.arrayOf(schema.string())), diff --git a/x-pack/plugins/case/server/routes/api/types.ts b/x-pack/plugins/case/server/routes/api/types.ts index 2d1a88bcf1429..5f1c207bf9829 100644 --- a/x-pack/plugins/case/server/routes/api/types.ts +++ b/x-pack/plugins/case/server/routes/api/types.ts @@ -32,12 +32,14 @@ export interface CaseAttributes extends NewCaseType, SavedObjectAttributes { export type FlattenedCaseSavedObject = CaseAttributes & { case_id: string; + version: string; comments: FlattenedCommentSavedObject[]; }; export type FlattenedCasesSavedObject = Array< CaseAttributes & { case_id: string; + version: string; // TO DO it is partial because we need to add it the commentCount commentCount?: number; } @@ -52,6 +54,7 @@ export interface AllCases { export type FlattenedCommentSavedObject = CommentAttributes & { comment_id: string; + version: string; // TO DO We might want to add the case_id where this comment is related too }; @@ -69,3 +72,13 @@ export interface UpdatedCaseType { title?: UpdatedCaseTyped['title']; updated_at: string; } + +export enum SortFieldCase { + createdAt = 'created_at', + state = 'state', + updatedAt = 'updated_at', +} + +export type Writable = { + -readonly [K in keyof T]: T[K]; +}; diff --git a/x-pack/plugins/case/server/routes/api/update_case.ts b/x-pack/plugins/case/server/routes/api/update_case.ts index 2a814c7259e4a..1c1a56dfe9b3a 100644 --- a/x-pack/plugins/case/server/routes/api/update_case.ts +++ b/x-pack/plugins/case/server/routes/api/update_case.ts @@ -5,9 +5,17 @@ */ import { schema } from '@kbn/config-schema'; +import { SavedObject } from 'kibana/server'; +import Boom from 'boom'; +import { difference } from 'lodash'; import { wrapError } from './utils'; import { RouteDeps } from '.'; -import { UpdatedCaseSchema } from './schema'; +import { UpdateCaseArguments } from './schema'; +import { CaseAttributes, UpdatedCaseTyped, Writable } from './types'; + +interface UpdateCase extends Writable { + [key: string]: any; +} export function initUpdateCaseApi({ caseService, router }: RouteDeps) { router.patch( @@ -17,23 +25,70 @@ export function initUpdateCaseApi({ caseService, router }: RouteDeps) { params: schema.object({ id: schema.string(), }), - body: UpdatedCaseSchema, + body: UpdateCaseArguments, }, }, async (context, request, response) => { + let theCase: SavedObject; try { - const updatedCase = await caseService.updateCase({ + theCase = await caseService.getCase({ client: context.core.savedObjects.client, caseId: request.params.id, - updatedAttributes: { - ...request.body, - updated_at: new Date().toISOString(), - }, }); - return response.ok({ body: updatedCase.attributes }); } catch (error) { return response.customError(wrapError(error)); } + + if (request.body.version !== theCase.version) { + return response.customError( + wrapError( + Boom.conflict( + 'This case has been updated. Please refresh before saving additional updates.' + ) + ) + ); + } + const currentCase = theCase.attributes; + const updateCase: Partial = Object.entries(request.body.case).reduce( + (acc, [key, value]) => { + const currentValue = currentCase[key]; + if ( + Array.isArray(value) && + Array.isArray(currentValue) && + difference(value, currentValue).length !== 0 + ) { + return { + ...acc, + [key]: value, + }; + } else if (value !== currentCase[key]) { + return { + ...acc, + [key]: value, + }; + } + return acc; + }, + {} + ); + if (Object.keys(updateCase).length > 0) { + try { + const updatedCase = await caseService.updateCase({ + client: context.core.savedObjects.client, + caseId: request.params.id, + updatedAttributes: { + ...updateCase, + updated_at: new Date().toISOString(), + }, + }); + return response.ok({ body: { ...updatedCase.attributes, version: updatedCase.version } }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + return response.customError( + wrapError(Boom.notAcceptable('All update fields are identical to current version.')) + ); } ); } diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index 51944b04836ab..32de41e1c01c5 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -20,6 +20,7 @@ import { AllCases, NewCaseType, NewCommentType, + SortFieldCase, UserType, } from './types'; @@ -80,6 +81,7 @@ export const flattenCaseSavedObject = ( comments: Array> ): FlattenedCaseSavedObject => ({ case_id: savedObject.id, + version: savedObject.version ? savedObject.version : '0', comments: flattenCommentSavedObjects(comments), ...savedObject.attributes, }); @@ -107,5 +109,21 @@ export const flattenCommentSavedObject = ( savedObject: SavedObject ): FlattenedCommentSavedObject => ({ comment_id: savedObject.id, + version: savedObject.version ? savedObject.version : '0', ...savedObject.attributes, }); + +export const sortToSnake = (sortField: string): SortFieldCase => { + switch (sortField) { + case 'state': + return SortFieldCase.state; + case 'createdAt': + case 'created_at': + return SortFieldCase.createdAt; + case 'updatedAt': + case 'updated_at': + return SortFieldCase.updatedAt; + default: + return SortFieldCase.createdAt; + } +}; diff --git a/x-pack/plugins/case/server/services/tags/read_tags.ts b/x-pack/plugins/case/server/services/tags/read_tags.ts index 58ab99b164cfb..da5905fe4ea35 100644 --- a/x-pack/plugins/case/server/services/tags/read_tags.ts +++ b/x-pack/plugins/case/server/services/tags/read_tags.ts @@ -52,7 +52,7 @@ export const readRawTags = async ({ page: 1, perPage, }); - const tags = await client.find({ + const tags = await client.find({ type: CASE_SAVED_OBJECT, fields: ['tags'], page: 1, diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts index 1e439b68f8c30..849bfda2bc86f 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts @@ -7,7 +7,6 @@ import uuid from 'uuid'; import { SavedObject, - SavedObjectAttributes, SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, @@ -42,7 +41,7 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon public readonly errors = options.baseClient.errors ) {} - public async create( + public async create( type: string, attributes: T = {} as T, options: SavedObjectsCreateOptions = {} @@ -66,15 +65,15 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon type, await this.options.service.encryptAttributes( { type, id, namespace: options.namespace }, - attributes + attributes as Record ), { ...options, id } ) - ); + ) as SavedObject; } - public async bulkCreate( - objects: SavedObjectsBulkCreateObject[], + public async bulkCreate( + objects: Array>, options?: SavedObjectsBaseOptions ) { // We encrypt attributes for every object in parallel and that can potentially exhaust libuv or @@ -101,14 +100,14 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon id, attributes: await this.options.service.encryptAttributes( { type: object.type, id, namespace: options && options.namespace }, - object.attributes + object.attributes as Record ), - }; + } as SavedObjectsBulkCreateObject; }) ); return this.stripEncryptedAttributesFromBulkResponse( - await this.options.baseClient.bulkCreate(encryptedObjects, options) + await this.options.baseClient.bulkCreate(encryptedObjects, options) ); } @@ -144,28 +143,28 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon return await this.options.baseClient.delete(type, id, options); } - public async find(options: SavedObjectsFindOptions) { + public async find(options: SavedObjectsFindOptions) { return this.stripEncryptedAttributesFromBulkResponse( - await this.options.baseClient.find(options) + await this.options.baseClient.find(options) ); } - public async bulkGet( + public async bulkGet( objects: SavedObjectsBulkGetObject[] = [], options?: SavedObjectsBaseOptions ) { return this.stripEncryptedAttributesFromBulkResponse( - await this.options.baseClient.bulkGet(objects, options) + await this.options.baseClient.bulkGet(objects, options) ); } - public async get(type: string, id: string, options?: SavedObjectsBaseOptions) { + public async get(type: string, id: string, options?: SavedObjectsBaseOptions) { return this.stripEncryptedAttributesFromResponse( - await this.options.baseClient.get(type, id, options) + await this.options.baseClient.get(type, id, options) ); } - public async update( + public async update( type: string, id: string, attributes: Partial, @@ -199,7 +198,7 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon if (this.options.service.isRegistered(response.type)) { response.attributes = this.options.service.stripEncryptedAttributes( response.type, - response.attributes + response.attributes as Record ); } @@ -218,7 +217,7 @@ export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientCon if (this.options.service.isRegistered(savedObject.type)) { savedObject.attributes = this.options.service.stripEncryptedAttributes( savedObject.type, - savedObject.attributes + savedObject.attributes as Record ); } } diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts index 80bd2ab7b5171..0dbdc2f3ac7e3 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - CoreSetup, - SavedObject, - SavedObjectAttributes, - SavedObjectsBaseOptions, -} from 'src/core/server'; +import { CoreSetup, SavedObject, SavedObjectsBaseOptions } from 'src/core/server'; import { EncryptedSavedObjectsService } from '../crypto'; import { EncryptedSavedObjectsClientWrapper } from './encrypted_saved_objects_client_wrapper'; @@ -20,7 +15,7 @@ interface SetupSavedObjectsParams { } export interface SavedObjectsSetup { - getDecryptedAsInternalUser: ( + getDecryptedAsInternalUser: ( type: string, id: string, options?: SavedObjectsBaseOptions @@ -47,7 +42,7 @@ export function setupSavedObjects({ core.savedObjects.createInternalRepository() ); return { - getDecryptedAsInternalUser: async ( + getDecryptedAsInternalUser: async ( type: string, id: string, options?: SavedObjectsBaseOptions @@ -56,10 +51,10 @@ export function setupSavedObjects({ const savedObject = await internalRepository.get(type, id, options); return { ...savedObject, - attributes: await service.decryptAttributes( + attributes: (await service.decryptAttributes( { type, id, namespace: options && options.namespace }, - savedObject.attributes - ), + savedObject.attributes as Record + )) as T, }; }, }; diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index ef3b522e71cbf..4c319127e6399 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -71,7 +71,7 @@ export interface EndpointResultList { } export interface AlertData { - '@timestamp': Date; + '@timestamp': string; agent: { id: string; version: string; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx index 8530d6206d398..c6c032c273543 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx @@ -8,16 +8,14 @@ import * as React from 'react'; import ReactDOM from 'react-dom'; import { CoreStart, AppMountParameters } from 'kibana/public'; import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; -import { Route, Switch, BrowserRouter, useLocation } from 'react-router-dom'; -import { Provider, useDispatch } from 'react-redux'; +import { Route, Switch, BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; import { Store } from 'redux'; -import { memo } from 'react'; +import { RouteCapture } from './view/route_capture'; import { appStoreFactory } from './store'; import { AlertIndex } from './view/alerts'; import { ManagementList } from './view/managing'; import { PolicyList } from './view/policy'; -import { AppAction } from './store/action'; -import { EndpointAppLocation } from './types'; /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. @@ -33,13 +31,6 @@ export function renderApp(coreStart: CoreStart, { appBasePath, element }: AppMou }; } -const RouteCapture = memo(({ children }) => { - const location: EndpointAppLocation = useLocation(); - const dispatch: (action: AppAction) => unknown = useDispatch(); - dispatch({ type: 'userChangedUrl', payload: location }); - return <>{children}; -}); - interface RouterProps { basename: string; store: Store; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts index 6ba7a34ae81d1..0aeeb6881ad96 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts @@ -14,6 +14,7 @@ import { coreMock } from 'src/core/public/mocks'; import { AlertResultList } from '../../../../../common/types'; import { isOnAlertPage } from './selectors'; import { createBrowserHistory } from 'history'; +import { mockAlertResultList } from './mock_alert_result_list'; describe('alert list tests', () => { let store: Store; @@ -28,37 +29,7 @@ describe('alert list tests', () => { describe('when the user navigates to the alert list page', () => { beforeEach(() => { coreStart.http.get.mockImplementation(async () => { - const response: AlertResultList = { - alerts: [ - { - '@timestamp': new Date(1542341895000), - agent: { - id: 'ced9c68e-b94a-4d66-bb4c-6106514f0a2f', - version: '3.0.0', - }, - event: { - action: 'open', - }, - file_classification: { - malware_classification: { - score: 3, - }, - }, - host: { - hostname: 'HD-c15-bc09190a', - ip: '10.179.244.14', - os: { - name: 'Windows', - }, - }, - thread: {}, - }, - ], - total: 1, - request_page_size: 10, - request_page_index: 0, - result_from_index: 0, - }; + const response: AlertResultList = mockAlertResultList(); return response; }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts index 77708a3c77e2b..5c257c3d65fdc 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts @@ -7,37 +7,47 @@ import { Store, createStore, applyMiddleware } from 'redux'; import { History } from 'history'; import { alertListReducer } from './reducer'; -import { AlertListState } from '../../types'; +import { AlertListState, AlertingIndexUIQueryParams } from '../../types'; import { alertMiddlewareFactory } from './middleware'; import { AppAction } from '../action'; import { coreMock } from 'src/core/public/mocks'; import { createBrowserHistory } from 'history'; -import { - urlFromNewPageSizeParam, - paginationDataFromUrl, - urlFromNewPageIndexParam, -} from './selectors'; +import { uiQueryParams } from './selectors'; +import { urlFromQueryParams } from '../../view/alerts/url_from_query_params'; describe('alert list pagination', () => { let store: Store; let coreStart: ReturnType; let history: History; + let queryParams: () => AlertingIndexUIQueryParams; + /** + * Update the history with a new `AlertingIndexUIQueryParams` + */ + let historyPush: (params: AlertingIndexUIQueryParams) => void; beforeEach(() => { coreStart = coreMock.createStart(); history = createBrowserHistory(); + const middleware = alertMiddlewareFactory(coreStart); store = createStore(alertListReducer, applyMiddleware(middleware)); + + history.listen(location => { + store.dispatch({ type: 'userChangedUrl', payload: location }); + }); + + queryParams = () => uiQueryParams(store.getState()); + + historyPush = (nextQueryParams: AlertingIndexUIQueryParams): void => { + return history.push(urlFromQueryParams(nextQueryParams)); + }; }); describe('when the user navigates to the alert list page', () => { describe('when a new page size is passed', () => { beforeEach(() => { - const urlPageSizeSelector = urlFromNewPageSizeParam(store.getState()); - history.push(urlPageSizeSelector(1)); - store.dispatch({ type: 'userChangedUrl', payload: history.location }); + historyPush({ ...queryParams(), page_size: '1' }); }); it('should modify the url correctly', () => { - const actualPaginationQuery = paginationDataFromUrl(store.getState()); - expect(actualPaginationQuery).toMatchInlineSnapshot(` + expect(queryParams()).toMatchInlineSnapshot(` Object { "page_size": "1", } @@ -46,13 +56,10 @@ describe('alert list pagination', () => { describe('and then a new page index is passed', () => { beforeEach(() => { - const urlPageIndexSelector = urlFromNewPageIndexParam(store.getState()); - history.push(urlPageIndexSelector(1)); - store.dispatch({ type: 'userChangedUrl', payload: history.location }); + historyPush({ ...queryParams(), page_index: '1' }); }); it('should modify the url in the correct order', () => { - const actualPaginationQuery = paginationDataFromUrl(store.getState()); - expect(actualPaginationQuery).toMatchInlineSnapshot(` + expect(queryParams()).toMatchInlineSnapshot(` Object { "page_index": "1", "page_size": "1", @@ -64,35 +71,15 @@ describe('alert list pagination', () => { describe('when a new page index is passed', () => { beforeEach(() => { - const urlPageIndexSelector = urlFromNewPageIndexParam(store.getState()); - history.push(urlPageIndexSelector(1)); - store.dispatch({ type: 'userChangedUrl', payload: history.location }); + historyPush({ ...queryParams(), page_index: '1' }); }); it('should modify the url correctly', () => { - const actualPaginationQuery = paginationDataFromUrl(store.getState()); - expect(actualPaginationQuery).toMatchInlineSnapshot(` + expect(queryParams()).toMatchInlineSnapshot(` Object { "page_index": "1", } `); }); - - describe('and then a new page size is passed', () => { - beforeEach(() => { - const urlPageSizeSelector = urlFromNewPageSizeParam(store.getState()); - history.push(urlPageSizeSelector(1)); - store.dispatch({ type: 'userChangedUrl', payload: history.location }); - }); - it('should modify the url correctly and reset index to `0`', () => { - const actualPaginationQuery = paginationDataFromUrl(store.getState()); - expect(actualPaginationQuery).toMatchInlineSnapshot(` - Object { - "page_index": "0", - "page_size": "1", - } - `); - }); - }); }); }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts index 059507c8f0658..76a6867418bd8 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HttpFetchQuery } from 'kibana/public'; import { AlertResultList } from '../../../../../common/types'; import { AppAction } from '../action'; import { MiddlewareFactory, AlertListState } from '../../types'; -import { isOnAlertPage, paginationDataFromUrl } from './selectors'; +import { isOnAlertPage, apiQueryParams } from './selectors'; export const alertMiddlewareFactory: MiddlewareFactory = coreStart => { return api => next => async (action: AppAction) => { @@ -16,7 +15,7 @@ export const alertMiddlewareFactory: MiddlewareFactory = coreSta const state = api.getState(); if (action.type === 'userChangedUrl' && isOnAlertPage(state)) { const response: AlertResultList = await coreStart.http.get(`/api/endpoint/alerts`, { - query: paginationDataFromUrl(state) as HttpFetchQuery, + query: apiQueryParams(state), }); api.dispatch({ type: 'serverReturnedAlertsData', payload: response }); } diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts new file mode 100644 index 0000000000000..338a1077b58a2 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AlertResultList } from '../../../../../common/types'; + +export const mockAlertResultList: (options?: { + total?: number; + request_page_size?: number; + request_page_index?: number; +}) => AlertResultList = (options = {}) => { + const { + total = 1, + request_page_size: requestPageSize = 10, + request_page_index: requestPageIndex = 0, + } = options; + + // Skip any that are before the page we're on + const numberToSkip = requestPageSize * requestPageIndex; + + // total - numberToSkip is the count of non-skipped ones, but return no more than a pageSize, and no less than 0 + const actualCountToReturn = Math.max(Math.min(total - numberToSkip, requestPageSize), 0); + + const alerts = []; + for (let index = 0; index < actualCountToReturn; index++) { + alerts.push({ + '@timestamp': new Date(1542341895000).toString(), + agent: { + id: 'ced9c68e-b94a-4d66-bb4c-6106514f0a2f', + version: '3.0.0', + }, + event: { + action: 'open', + }, + file_classification: { + malware_classification: { + score: 3, + }, + }, + host: { + hostname: 'HD-c15-bc09190a', + ip: '10.179.244.14', + os: { + name: 'Windows', + }, + }, + thread: {}, + }); + } + const mock: AlertResultList = { + alerts, + total, + request_page_size: requestPageSize, + request_page_index: requestPageIndex, + result_from_index: 0, + }; + return mock; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts index 6ad053888729c..3a0461e06538f 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts @@ -4,9 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import qs from 'querystring'; -import { AlertListState } from '../../types'; +import querystring from 'querystring'; +import { + createSelector, + createStructuredSelector as createStructuredSelectorWithBadType, +} from 'reselect'; +import { Immutable } from '../../../../../common/types'; +import { + AlertListState, + AlertingIndexUIQueryParams, + AlertsAPIQueryParams, + CreateStructuredSelector, +} from '../../types'; +const createStructuredSelector: CreateStructuredSelector = createStructuredSelectorWithBadType; /** * Returns the Alert Data array from state */ @@ -15,14 +26,12 @@ export const alertListData = (state: AlertListState) => state.alerts; /** * Returns the alert list pagination data from state */ -export const alertListPagination = (state: AlertListState) => { - return { - pageIndex: state.request_page_index, - pageSize: state.request_page_size, - resultFromIndex: state.result_from_index, - total: state.total, - }; -}; +export const alertListPagination = createStructuredSelector({ + pageIndex: (state: AlertListState) => state.request_page_index, + pageSize: (state: AlertListState) => state.request_page_size, + resultFromIndex: (state: AlertListState) => state.result_from_index, + total: (state: AlertListState) => state.total, +}); /** * Returns a boolean based on whether or not the user is on the alerts page @@ -32,48 +41,55 @@ export const isOnAlertPage = (state: AlertListState): boolean => { }; /** - * Returns the query object received from parsing the URL query params - */ -export const paginationDataFromUrl = (state: AlertListState): qs.ParsedUrlQuery => { - if (state.location) { - // Removes the `?` from the beginning of query string if it exists - const query = qs.parse(state.location.search.slice(1)); - return { - ...(query.page_size ? { page_size: query.page_size } : {}), - ...(query.page_index ? { page_index: query.page_index } : {}), - }; - } else { - return {}; - } -}; - -/** - * Returns a function that takes in a new page size and returns a new query param string + * Returns the query object received from parsing the browsers URL query params. + * Used to calculate urls for links and such. */ -export const urlFromNewPageSizeParam: ( +export const uiQueryParams: ( state: AlertListState -) => (newPageSize: number) => string = state => { - return newPageSize => { - const urlPaginationData = paginationDataFromUrl(state); - urlPaginationData.page_size = newPageSize.toString(); +) => Immutable = createSelector( + (state: AlertListState) => state.location, + (location: AlertListState['location']) => { + const data: AlertingIndexUIQueryParams = {}; + if (location) { + // Removes the `?` from the beginning of query string if it exists + const query = querystring.parse(location.search.slice(1)); - // Only set the url back to page zero if the user has changed the page index already - if (urlPaginationData.page_index !== undefined) { - urlPaginationData.page_index = '0'; + /** + * Build an AlertingIndexUIQueryParams object with keys from the query. + * If more than one value exists for a key, use the last. + */ + const keys: Array = [ + 'page_size', + 'page_index', + 'selected_alert', + ]; + for (const key of keys) { + const value = query[key]; + if (typeof value === 'string') { + data[key] = value; + } else if (Array.isArray(value)) { + data[key] = value[value.length - 1]; + } + } } - return '?' + qs.stringify(urlPaginationData); - }; -}; + return data; + } +); /** - * Returns a function that takes in a new page index and returns a new query param string + * query params to use when requesting alert data. */ -export const urlFromNewPageIndexParam: ( +export const apiQueryParams: ( state: AlertListState -) => (newPageIndex: number) => string = state => { - return newPageIndex => { - const urlPaginationData = paginationDataFromUrl(state); - urlPaginationData.page_index = newPageIndex.toString(); - return '?' + qs.stringify(urlPaginationData); - }; -}; +) => Immutable = createSelector( + uiQueryParams, + ({ page_size, page_index }) => ({ + page_size, + page_index, + }) +); + +export const hasSelectedAlert: (state: AlertListState) => boolean = createSelector( + uiQueryParams, + ({ selected_alert: selectedAlert }) => selectedAlert !== undefined +); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts index 3aeeeaf1c09e2..b95ff7cb2d45c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/index.ts @@ -48,25 +48,36 @@ export const substateMiddlewareFactory = ( }; }; -export const appStoreFactory = (coreStart: CoreStart): Store => { +export const appStoreFactory: ( + /** + * Allow middleware to communicate with Kibana core. + */ + coreStart: CoreStart, + /** + * Create the store without any middleware. This is useful for testing the store w/o side effects. + */ + disableMiddleware?: boolean +) => Store = (coreStart, disableMiddleware = false) => { const store = createStore( appReducer, - composeWithReduxDevTools( - applyMiddleware( - substateMiddlewareFactory( - globalState => globalState.managementList, - managementMiddlewareFactory(coreStart) - ), - substateMiddlewareFactory( - globalState => globalState.policyList, - policyListMiddlewareFactory(coreStart) - ), - substateMiddlewareFactory( - globalState => globalState.alertList, - alertMiddlewareFactory(coreStart) + disableMiddleware + ? undefined + : composeWithReduxDevTools( + applyMiddleware( + substateMiddlewareFactory( + globalState => globalState.managementList, + managementMiddlewareFactory(coreStart) + ), + substateMiddlewareFactory( + globalState => globalState.policyList, + policyListMiddlewareFactory(coreStart) + ), + substateMiddlewareFactory( + globalState => globalState.alertList, + alertMiddlewareFactory(coreStart) + ) + ) ) - ) - ) ); return store; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index d07521d09a119..bd4838419891d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -10,6 +10,7 @@ import { EndpointMetadata } from '../../../common/types'; import { AppAction } from './store/action'; import { AlertResultList, Immutable } from '../../../common/types'; +export { AppAction }; export type MiddlewareFactory = ( coreStart: CoreStart ) => ( @@ -63,6 +64,9 @@ export interface GlobalState { readonly policyList: PolicyListState; } +/** + * A better type for createStructuredSelector. This doesn't support the options object. + */ export type CreateStructuredSelector = < SelectorMap extends { [key: string]: (...args: never[]) => unknown } >( @@ -76,7 +80,6 @@ export type CreateStructuredSelector = < export interface EndpointAppLocation { pathname: string; search: string; - state: never; hash: string; key?: string; } @@ -85,3 +88,35 @@ export type AlertListData = AlertResultList; export type AlertListState = Immutable & { readonly location?: Immutable; }; + +/** + * Gotten by parsing the URL from the browser. Used to calculate the new URL when changing views. + */ +export interface AlertingIndexUIQueryParams { + /** + * How many items to show in list. + */ + page_size?: string; + /** + * Which page to show. If `page_index` is 1, show page 2. + */ + page_index?: string; + /** + * If any value is present, show the alert detail view for the selected alert. Should be an ID for an alert event. + */ + selected_alert?: string; +} + +/** + * Query params to pass to the alert API when fetching new data. + */ +export interface AlertsAPIQueryParams { + /** + * Number of results to return. + */ + page_size?: string; + /** + * 0-based index of 'page' to return. + */ + page_index?: string; +} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx new file mode 100644 index 0000000000000..37847553d512a --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx @@ -0,0 +1,189 @@ +/* + * 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 React from 'react'; +import * as reactTestingLibrary from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { I18nProvider } from '@kbn/i18n/react'; +import { AlertIndex } from './index'; +import { appStoreFactory } from '../../store'; +import { coreMock } from 'src/core/public/mocks'; +import { fireEvent, waitForElement, act } from '@testing-library/react'; +import { RouteCapture } from '../route_capture'; +import { createMemoryHistory, MemoryHistory } from 'history'; +import { Router } from 'react-router-dom'; +import { AppAction } from '../../types'; +import { mockAlertResultList } from '../../store/alerts/mock_alert_result_list'; + +describe('when on the alerting page', () => { + let render: () => reactTestingLibrary.RenderResult; + let history: MemoryHistory; + let store: ReturnType; + + /** + * @testing-library/react provides `queryByTestId`, but that uses the data attribute + * 'data-testid' whereas our FTR and EUI's tests all use 'data-test-subj'. While @testing-library/react + * could be configured to use 'data-test-subj', it is not currently configured that way. + * + * This provides an equivalent function to `queryByTestId` but that uses our 'data-test-subj' attribute. + */ + let queryByTestSubjId: ( + renderResult: reactTestingLibrary.RenderResult, + testSubjId: string + ) => Promise; + + beforeEach(async () => { + /** + * Create a 'history' instance that is only in-memory and causes no side effects to the testing environment. + */ + history = createMemoryHistory(); + /** + * Create a store, with the middleware disabled. We don't want side effects being created by our code in this test. + */ + store = appStoreFactory(coreMock.createStart(), true); + /** + * Render the test component, use this after setting up anything in `beforeEach`. + */ + render = () => { + /** + * Provide the store via `Provider`, and i18n APIs via `I18nProvider`. + * Use react-router via `Router`, passing our in-memory `history` instance. + * Use `RouteCapture` to emit url-change actions when the URL is changed. + * Finally, render the `AlertIndex` component which we are testing. + */ + return reactTestingLibrary.render( + + + + + + + + + + ); + }; + queryByTestSubjId = async (renderResult, testSubjId) => { + return await waitForElement( + /** + * Use document.body instead of container because EUI renders things like popover out of the DOM heirarchy. + */ + () => document.body.querySelector(`[data-test-subj="${testSubjId}"]`), + { + container: renderResult.container, + } + ); + }; + }); + it('should show a data grid', async () => { + await render().findByTestId('alertListGrid'); + }); + describe('when there is no selected alert in the url', () => { + it('should not show the flyout', () => { + expect(render().queryByTestId('alertDetailFlyout')).toBeNull(); + }); + describe('when data loads', () => { + beforeEach(() => { + /** + * Dispatch the `serverReturnedAlertsData` action, which is normally dispatched by the middleware + * after interacting with the server. + */ + reactTestingLibrary.act(() => { + const action: AppAction = { + type: 'serverReturnedAlertsData', + payload: mockAlertResultList(), + }; + store.dispatch(action); + }); + }); + it('should render the alert summary row in the grid', async () => { + const renderResult = render(); + const rows = await renderResult.findAllByRole('row'); + + /** + * There should be a 'row' which is the header, and + * row which is the alert item. + */ + expect(rows).toHaveLength(2); + }); + describe('when the user has clicked the alert type in the grid', () => { + let renderResult: reactTestingLibrary.RenderResult; + beforeEach(async () => { + renderResult = render(); + /** + * This is the cell with the alert type, it has a link. + */ + fireEvent.click(await renderResult.findByTestId('alertTypeCellLink')); + }); + it('should show the flyout', async () => { + await renderResult.findByTestId('alertDetailFlyout'); + }); + }); + }); + }); + describe('when there is a selected alert in the url', () => { + beforeEach(() => { + reactTestingLibrary.act(() => { + history.push({ + ...history.location, + search: '?selected_alert=1', + }); + }); + }); + it('should show the flyout', async () => { + await render().findByTestId('alertDetailFlyout'); + }); + describe('when the user clicks the close button on the flyout', () => { + let renderResult: reactTestingLibrary.RenderResult; + beforeEach(async () => { + renderResult = render(); + /** + * Use our helper function to find the flyout's close button, as it uses a different test ID attribute. + */ + const closeButton = await queryByTestSubjId(renderResult, 'euiFlyoutCloseButton'); + if (closeButton) { + fireEvent.click(closeButton); + } + }); + it('should no longer show the flyout', () => { + expect(render().queryByTestId('alertDetailFlyout')).toBeNull(); + }); + }); + }); + describe('when the url has page_size=1 and a page_index=1', () => { + beforeEach(() => { + reactTestingLibrary.act(() => { + history.push({ + ...history.location, + search: '?page_size=1&page_index=1', + }); + }); + }); + describe('when the user changes page size to 10', () => { + beforeEach(async () => { + const renderResult = render(); + const paginationButton = await queryByTestSubjId( + renderResult, + 'tablePaginationPopoverButton' + ); + if (paginationButton) { + act(() => { + fireEvent.click(paginationButton); + }); + } + const show10RowsButton = await queryByTestSubjId(renderResult, 'tablePagination-10-rows'); + if (show10RowsButton) { + act(() => { + fireEvent.click(show10RowsButton); + }); + } + }); + it('should have a page_index of 0', () => { + expect(history.location.search).toBe('?page_size=10'); + }); + }); + }); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx index 045b82200b11b..6f88727575557 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx @@ -6,16 +6,30 @@ import { memo, useState, useMemo, useCallback } from 'react'; import React from 'react'; -import { EuiDataGrid, EuiDataGridColumn, EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui'; +import { + EuiDataGrid, + EuiDataGridColumn, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiTitle, + EuiBadge, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useHistory } from 'react-router-dom'; +import { useHistory, Link } from 'react-router-dom'; +import { FormattedDate } from 'react-intl'; +import { urlFromQueryParams } from './url_from_query_params'; +import { AlertData } from '../../../../../common/types'; import * as selectors from '../../store/alerts/selectors'; import { useAlertListSelector } from './hooks/use_alerts_selector'; export const AlertIndex = memo(() => { const history = useHistory(); - const columns: EuiDataGridColumn[] = useMemo(() => { + const columns = useMemo((): EuiDataGridColumn[] => { return [ { id: 'alert_type', @@ -69,22 +83,48 @@ export const AlertIndex = memo(() => { }, []); const { pageIndex, pageSize, total } = useAlertListSelector(selectors.alertListPagination); - const urlFromNewPageSizeParam = useAlertListSelector(selectors.urlFromNewPageSizeParam); - const urlFromNewPageIndexParam = useAlertListSelector(selectors.urlFromNewPageIndexParam); const alertListData = useAlertListSelector(selectors.alertListData); + const hasSelectedAlert = useAlertListSelector(selectors.hasSelectedAlert); + const queryParams = useAlertListSelector(selectors.uiQueryParams); const onChangeItemsPerPage = useCallback( - newPageSize => history.push(urlFromNewPageSizeParam(newPageSize)), - [history, urlFromNewPageSizeParam] + newPageSize => { + const newQueryParms = { ...queryParams }; + newQueryParms.page_size = newPageSize; + delete newQueryParms.page_index; + const relativeURL = urlFromQueryParams(newQueryParms); + return history.push(relativeURL); + }, + [history, queryParams] ); const onChangePage = useCallback( - newPageIndex => history.push(urlFromNewPageIndexParam(newPageIndex)), - [history, urlFromNewPageIndexParam] + newPageIndex => { + return history.push( + urlFromQueryParams({ + ...queryParams, + page_index: newPageIndex, + }) + ); + }, + [history, queryParams] ); const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id)); + const handleFlyoutClose = useCallback(() => { + const { selected_alert, ...paramsWithoutSelectedAlert } = queryParams; + history.push(urlFromQueryParams(paramsWithoutSelectedAlert)); + }, [history, queryParams]); + + const datesForRows: Map = useMemo(() => { + return new Map( + alertListData.map(alertData => { + return [alertData, new Date(alertData['@timestamp'])]; + }) + ); + }, [alertListData]); + const renderCellValue = useMemo(() => { return ({ rowIndex, columnId }: { rowIndex: number; columnId: string }) => { if (rowIndex > total) { @@ -94,11 +134,18 @@ export const AlertIndex = memo(() => { const row = alertListData[rowIndex % pageSize]; if (columnId === 'alert_type') { - return i18n.translate( - 'xpack.endpoint.application.endpoint.alerts.alertType.maliciousFileDescription', - { - defaultMessage: 'Malicious File', - } + return ( + + {i18n.translate( + 'xpack.endpoint.application.endpoint.alerts.alertType.maliciousFileDescription', + { + defaultMessage: 'Malicious File', + } + )} + ); } else if (columnId === 'event_type') { return row.event.action; @@ -109,7 +156,31 @@ export const AlertIndex = memo(() => { } else if (columnId === 'host_name') { return row.host.hostname; } else if (columnId === 'timestamp') { - return row['@timestamp']; + const date = datesForRows.get(row)!; + if (date && isFinite(date.getTime())) { + return ( + + ); + } else { + return ( + + {i18n.translate( + 'xpack.endpoint.application.endpoint.alerts.alertDate.timestampInvalidLabel', + { + defaultMessage: 'invalid', + } + )} + + ); + } } else if (columnId === 'archived') { return null; } else if (columnId === 'malware_score') { @@ -117,7 +188,7 @@ export const AlertIndex = memo(() => { } return null; }; - }, [alertListData, pageSize, total]); + }, [alertListData, datesForRows, pageSize, queryParams, total]); const pagination = useMemo(() => { return { @@ -130,23 +201,43 @@ export const AlertIndex = memo(() => { }, [onChangeItemsPerPage, onChangePage, pageIndex, pageSize]); return ( - - - - - - - + <> + {hasSelectedAlert && ( + + + +

+ {i18n.translate('xpack.endpoint.application.endpoint.alerts.detailsTitle', { + defaultMessage: 'Alert Details', + })} +

+
+
+ +
+ )} + + + + ({ + visibleColumns, + setVisibleColumns, + }), + [setVisibleColumns, visibleColumns] + )} + renderCellValue={renderCellValue} + pagination={pagination} + data-test-subj="alertListGrid" + data-testid="alertListGrid" + /> + + + + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/url_from_query_params.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/url_from_query_params.ts new file mode 100644 index 0000000000000..e037d000e6e8f --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/url_from_query_params.ts @@ -0,0 +1,31 @@ +/* + * 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 querystring from 'querystring'; +import { AlertingIndexUIQueryParams, EndpointAppLocation } from '../../types'; + +/** + * Return a relative URL for `AlertingIndexUIQueryParams`. + * usage: + * + * ```ts + * // Replace this with however you get state, e.g. useSelector in react + * const queryParams = selectors.uiQueryParams(store.getState()) + * + * // same as current url, but page_index is now 3 + * const relativeURL = urlFromQueryParams({ ...queryParams, page_index: 3 }) + * + * // now use relativeURL in the 'href' of a link, the 'to' of a react-router-dom 'Link' or history.push, history.replace + * ``` + */ +export function urlFromQueryParams( + queryParams: AlertingIndexUIQueryParams +): Partial { + const search = querystring.stringify(queryParams); + return { + search, + }; +} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/route_capture.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/route_capture.tsx new file mode 100644 index 0000000000000..28d2019b56888 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/route_capture.tsx @@ -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 React, { memo } from 'react'; +import { useLocation } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; +import { EndpointAppLocation, AppAction } from '../types'; + +/** + * This component should be used above all routes, but below the Provider. + * It dispatches actions when the URL is changed. + */ +export const RouteCapture = memo(({ children }) => { + const location: EndpointAppLocation = useLocation(); + const dispatch: (action: AppAction) => unknown = useDispatch(); + dispatch({ type: 'userChangedUrl', payload: location }); + return <>{children}; +}); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts index 4d12e656205fa..25d08a8c347ed 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts @@ -31,7 +31,6 @@ export const inverseProjectionMatrix = composeSelectors( /** * The scale by which world values are scaled when rendered. - * TODO make it a number */ export const scale = composeSelectors(cameraStateSelector, cameraSelectors.scale); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/use_camera.test.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/use_camera.test.tsx index 85e1d4e694b15..f4abb51f062f2 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/use_camera.test.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/use_camera.test.tsx @@ -4,9 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/** - * This import must be hoisted as it uses `jest.mock`. Is there a better way? Mocking is not good. - */ import React from 'react'; import { render, act, RenderResult, fireEvent } from '@testing-library/react'; import { useCamera } from './use_camera'; diff --git a/x-pack/plugins/endpoint/server/services/endpoint/alert_query_builders.test.ts b/x-pack/plugins/endpoint/server/services/endpoint/alert_query_builders.test.ts index a4d7de8fdcfdb..3ef1142b9ce46 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/alert_query_builders.test.ts +++ b/x-pack/plugins/endpoint/server/services/endpoint/alert_query_builders.test.ts @@ -16,26 +16,36 @@ describe('test query builder', () => { config: () => Promise.resolve(EndpointConfigSchema.validate({})), }; const queryParams = await getPagingProperties(mockRequest, mockCtx); - const query = await buildAlertListESQuery(queryParams); + const query = buildAlertListESQuery(queryParams); - expect(query).toEqual({ - body: { - query: { - match_all: {}, - }, - sort: [ - { - '@timestamp': { - order: 'desc', + expect(query).toMatchInlineSnapshot(` + Object { + "body": Object { + "query": Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "event.kind": "alert", + }, + }, + ], }, }, - ], - track_total_hits: 10000, - }, - from: 0, - size: 10, - index: 'my-index', - } as Record); + "sort": Array [ + Object { + "@timestamp": Object { + "order": "desc", + }, + }, + ], + "track_total_hits": 10000, + }, + "from": 0, + "index": "my-index", + "size": 10, + } + `); }); it('should adjust track_total_hits for deep pagination', async () => { const mockRequest = httpServerMock.createKibanaRequest({ @@ -49,26 +59,36 @@ describe('test query builder', () => { config: () => Promise.resolve(EndpointConfigSchema.validate({})), }; const queryParams = await getPagingProperties(mockRequest, mockCtx); - const query = await buildAlertListESQuery(queryParams); + const query = buildAlertListESQuery(queryParams); - expect(query).toEqual({ - body: { - query: { - match_all: {}, - }, - sort: [ - { - '@timestamp': { - order: 'desc', + expect(query).toMatchInlineSnapshot(` + Object { + "body": Object { + "query": Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "event.kind": "alert", + }, + }, + ], }, }, - ], - track_total_hits: 12000, - }, - from: 10000, - size: 1000, - index: 'my-index', - } as Record); + "sort": Array [ + Object { + "@timestamp": Object { + "order": "desc", + }, + }, + ], + "track_total_hits": 12000, + }, + "from": 10000, + "index": "my-index", + "size": 1000, + } + `); }); }); }); diff --git a/x-pack/plugins/endpoint/server/services/endpoint/alert_query_builders.ts b/x-pack/plugins/endpoint/server/services/endpoint/alert_query_builders.ts index a20f2ae1cdecd..e56ae43ef5c76 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/alert_query_builders.ts +++ b/x-pack/plugins/endpoint/server/services/endpoint/alert_query_builders.ts @@ -7,9 +7,9 @@ import { KibanaRequest } from 'kibana/server'; import { EndpointAppConstants } from '../../../common/types'; import { EndpointAppContext, AlertRequestParams, JSONish } from '../../types'; -export const buildAlertListESQuery = async ( +export const buildAlertListESQuery: ( pagingProperties: Record -): Promise => { +) => JSONish = pagingProperties => { const DEFAULT_TOTAL_HITS = 10000; // Calculate minimum total hits set to indicate there's a next page @@ -22,7 +22,15 @@ export const buildAlertListESQuery = async ( body: { track_total_hits: totalHitsMin, query: { - match_all: {}, + bool: { + must: [ + { + match: { + 'event.kind': 'alert', + }, + }, + ], + }, }, sort: [ { diff --git a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/index.ts b/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/index.ts index af50562bd3242..0e40fd335dd31 100644 --- a/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/index.ts +++ b/x-pack/plugins/es_ui_shared/console_lang/ace/modes/x_json/worker/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore import src from '!!raw-loader!./worker.js'; export const workerModule = { diff --git a/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx index 80d1b67e59798..2c553b57dcd48 100644 --- a/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx @@ -5,12 +5,13 @@ */ import { useState, useCallback } from 'react'; -import { SavedObjectAttributes, SavedObjectsBatchResponse } from 'src/core/public'; +import { SavedObjectsBatchResponse } from 'src/core/public'; import { useKibana } from '../../../../../src/plugins/kibana_react/public'; export const useBulkGetSavedObject = (type: string) => { const kibana = useKibana(); - const [data, setData] = useState | null>(null); + // TODO: define saved object type + const [data, setData] = useState | null>(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(false); diff --git a/x-pack/plugins/lens/server/routes/existing_fields.test.ts b/x-pack/plugins/lens/server/routes/existing_fields.test.ts index 1f19671826807..9bd11b6863d93 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.test.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.test.ts @@ -91,6 +91,8 @@ describe('buildFieldList', () => { type: 'indexpattern', attributes: { title: 'testpattern', + type: 'type', + typeMeta: 'typemeta', fields: JSON.stringify([ { name: 'foo', scripted: true, lang: 'painless', script: '2+2' }, { name: 'bar' }, diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index a059e32585909..40b2766a647cf 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -9,7 +9,10 @@ import { schema } from '@kbn/config-schema'; import { IScopedClusterClient, SavedObject, RequestHandlerContext } from 'src/core/server'; import { CoreSetup } from 'src/core/server'; import { BASE_API_URL } from '../../common'; -import { IndexPatternsFetcher } from '../../../../../src/plugins/data/server'; +import { + IndexPatternsFetcher, + IndexPatternAttributes, +} from '../../../../../src/plugins/data/server'; /** * The number of docs to sample to determine field empty status. @@ -125,7 +128,10 @@ async function fetchFieldExistence({ async function fetchIndexPatternDefinition(indexPatternId: string, context: RequestHandlerContext) { const savedObjectsClient = context.core.savedObjects.client; const requestClient = context.core.elasticsearch.dataClient; - const indexPattern = await savedObjectsClient.get('index-pattern', indexPatternId); + const indexPattern = await savedObjectsClient.get( + 'index-pattern', + indexPatternId + ); const indexPatternTitle = indexPattern.attributes.title; // TODO: maybe don't use IndexPatternsFetcher at all, since we're only using it // to look up field values in the resulting documents. We can accomplish the same @@ -155,7 +161,7 @@ async function fetchIndexPatternDefinition(indexPatternId: string, context: Requ * Exported only for unit tests. */ export function buildFieldList( - indexPattern: SavedObject, + indexPattern: SavedObject, mappings: MappingResult, fieldDescriptors: FieldDescriptor[] ): Field[] { diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index 03b1d770fa770..2209e7fb66fcb 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -5,7 +5,6 @@ */ import { - SavedObjectAttributes, SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, @@ -46,7 +45,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra this.checkSavedObjectsPrivilegesAsCurrentUser = checkSavedObjectsPrivilegesAsCurrentUser; } - public async create( + public async create( type: string, attributes: T = {} as T, options: SavedObjectsCreateOptions = {} @@ -56,8 +55,8 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra return await this.baseClient.create(type, attributes, options); } - public async bulkCreate( - objects: SavedObjectsBulkCreateObject[], + public async bulkCreate( + objects: Array>, options: SavedObjectsBaseOptions = {} ) { await this.ensureAuthorized( @@ -76,13 +75,13 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra return await this.baseClient.delete(type, id, options); } - public async find(options: SavedObjectsFindOptions) { + public async find(options: SavedObjectsFindOptions) { await this.ensureAuthorized(options.type, 'find', options.namespace, { options }); - return this.baseClient.find(options); + return this.baseClient.find(options); } - public async bulkGet( + public async bulkGet( objects: SavedObjectsBulkGetObject[] = [], options: SavedObjectsBaseOptions = {} ) { @@ -91,16 +90,16 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra options, }); - return await this.baseClient.bulkGet(objects, options); + return await this.baseClient.bulkGet(objects, options); } - public async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + public async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { await this.ensureAuthorized(type, 'get', options.namespace, { type, id, options }); - return await this.baseClient.get(type, id, options); + return await this.baseClient.get(type, id, options); } - public async update( + public async update( type: string, id: string, attributes: Partial, @@ -116,8 +115,8 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra return await this.baseClient.update(type, id, attributes, options); } - public async bulkUpdate( - objects: SavedObjectsBulkUpdateObject[] = [], + public async bulkUpdate( + objects: Array> = [], options: SavedObjectsBaseOptions = {} ) { await this.ensureAuthorized( @@ -127,7 +126,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra { objects, options } ); - return await this.baseClient.bulkUpdate(objects, options); + return await this.baseClient.bulkUpdate(objects, options); } private async checkPrivileges(actions: string | string[], namespace?: string) { diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts index 62a78679175b3..534d797123940 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.ts @@ -5,7 +5,6 @@ */ import { - SavedObjectAttributes, SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, @@ -65,14 +64,14 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - public async create( + public async create( type: string, attributes: T = {} as T, options: SavedObjectsCreateOptions = {} ) { throwErrorIfNamespaceSpecified(options); - return await this.client.create(type, attributes, { + return await this.client.create(type, attributes, { ...options, namespace: spaceIdToNamespace(this.spaceId), }); @@ -87,8 +86,8 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes, error: { message } }]} */ - public async bulkCreate( - objects: SavedObjectsBulkCreateObject[], + public async bulkCreate( + objects: Array>, options: SavedObjectsBaseOptions = {} ) { throwErrorIfNamespaceSpecified(options); @@ -133,10 +132,10 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { * @property {object} [options.hasReference] - { type, id } * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ - public async find(options: SavedObjectsFindOptions) { + public async find(options: SavedObjectsFindOptions) { throwErrorIfNamespaceSpecified(options); - return await this.client.find({ + return await this.client.find({ ...options, type: (options.type ? coerceToArray(options.type) : this.types).filter( type => type !== 'space' @@ -159,13 +158,13 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { * { id: 'foo', type: 'index-pattern' } * ]) */ - public async bulkGet( + public async bulkGet( objects: SavedObjectsBulkGetObject[] = [], options: SavedObjectsBaseOptions = {} ) { throwErrorIfNamespaceSpecified(options); - return await this.client.bulkGet(objects, { + return await this.client.bulkGet(objects, { ...options, namespace: spaceIdToNamespace(this.spaceId), }); @@ -180,10 +179,10 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ - public async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + public async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { throwErrorIfNamespaceSpecified(options); - return await this.client.get(type, id, { + return await this.client.get(type, id, { ...options, namespace: spaceIdToNamespace(this.spaceId), }); @@ -199,7 +198,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { * @property {string} [options.namespace] * @returns {promise} */ - public async update( + public async update( type: string, id: string, attributes: Partial, @@ -225,8 +224,8 @@ export class SpacesSavedObjectsClient implements SavedObjectsClientContract { * { id: 'foo', type: 'index-pattern', attributes: {} } * ]) */ - public async bulkUpdate( - objects: SavedObjectsBulkUpdateObject[] = [], + public async bulkUpdate( + objects: Array> = [], options: SavedObjectsBaseOptions = {} ) { throwErrorIfNamespaceSpecified(options); diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 6ed6ae16fa0f9..97794311fb3d2 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -18,11 +18,7 @@ import { } from './task'; import { StoreOpts, OwnershipClaimingOpts, TaskStore, SearchOpts } from './task_store'; import { savedObjectsRepositoryMock } from '../../../../src/core/server/mocks'; -import { - SavedObjectsSerializer, - SavedObjectTypeRegistry, - SavedObjectAttributes, -} from '../../../../src/core/server'; +import { SavedObjectsSerializer, SavedObjectTypeRegistry } from '../../../../src/core/server'; import { SavedObjectsErrorHelpers } from '../../../../src/core/server/saved_objects/service/lib/errors'; import { asTaskClaimEvent, TaskEvent } from './task_events'; import { asOk, asErr } from './lib/result_type'; @@ -64,15 +60,13 @@ describe('TaskStore', () => { describe('schedule', () => { async function testSchedule(task: TaskInstance) { const callCluster = jest.fn(); - savedObjectsClient.create.mockImplementation( - async (type: string, attributes: SavedObjectAttributes) => ({ - id: 'testid', - type, - attributes, - references: [], - version: '123', - }) - ); + savedObjectsClient.create.mockImplementation(async (type: string, attributes: any) => ({ + id: 'testid', + type, + attributes, + references: [], + version: '123', + })); const store = new TaskStore({ index: 'tasky', taskManagerId: '', @@ -155,14 +149,14 @@ describe('TaskStore', () => { test('sets runAt to now if not specified', async () => { await testSchedule({ taskType: 'dernstraight', params: {}, state: {} }); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); - const attributes = savedObjectsClient.create.mock.calls[0][1]; + const attributes: any = savedObjectsClient.create.mock.calls[0][1]; expect(new Date(attributes.runAt as string).getTime()).toEqual(mockedDate.getTime()); }); test('ensures params and state are not null', async () => { await testSchedule({ taskType: 'yawn' } as any); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); - const attributes = savedObjectsClient.create.mock.calls[0][1]; + const attributes: any = savedObjectsClient.create.mock.calls[0][1]; expect(attributes.params).toEqual('{}'); expect(attributes.state).toEqual('{}'); }); @@ -751,7 +745,7 @@ if (doc['task.runAt'].size()!=0) { }; savedObjectsClient.update.mockImplementation( - async (type: string, id: string, attributes: SavedObjectAttributes) => { + async (type: string, id: string, attributes: any) => { return { id, type, diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index 3915eeffc5519..0e487386eb04d 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -428,7 +428,8 @@ function taskInstanceToAttributes(doc: TaskInstance): SavedObjectAttributes { } export function savedObjectToConcreteTaskInstance( - savedObject: Omit + // TODO: define saved object type + savedObject: Omit, 'references'> ): ConcreteTaskInstance { return { ...savedObject.attributes, diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts index 071067ffa85cb..3529d8f3ae9c9 100644 --- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts @@ -59,7 +59,9 @@ export function resolveCopyToSpaceConflictsSuite( .then((response: any) => response.body); }; - const getObjectsAtSpace = async (spaceId: string): Promise<[SavedObject, SavedObject]> => { + const getObjectsAtSpace = async ( + spaceId: string + ): Promise<[SavedObject, SavedObject]> => { const dashboard = await getDashboardAtSpace(spaceId); const visualization = await getVisualizationAtSpace(spaceId); return [dashboard, visualization];