Retrieves a paginated subset of cases in the default space. (findCasesDefaultSpace)
+
Up
-
get /s/{spaceId}/api/cases/{caseId}/user_actions
-
Returns all user activity for a case. (getCaseActivity)
-
Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're seeking.
+
get /s/{spaceId}/api/cases/{caseId}
+
Retrieves information about a case. (getCase)
+
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're seeking.
Path parameters
@@ -1133,11 +1860,17 @@ Any modifications made to this file will be overwritten.
+
Query parameters
+
+
includeComments (optional)
+
+
Query Parameter — Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned. default: true
+
Return type
@@ -1147,17 +1880,57 @@ Any modifications made to this file will be overwritten.
Content-Type: application/json
{
"owner" : "cases",
- "action_id" : "22fd3e30-03b1-11ed-920c-974bfa104448",
- "case_id" : "22df07d0-03b1-11ed-920c-974bfa104448",
- "action" : "create",
+ "totalComment" : 0,
+ "settings" : {
+ "syncAlerts" : true
+ },
+ "totalAlerts" : 0,
+ "closed_at" : "2000-01-23T04:56:07.000+00:00",
+ "comments" : [ null, null ],
+ "assignees" : [ {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ }, {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ } ],
"created_at" : "2022-05-13T09:16:17.416Z",
- "comment_id" : "578608d0-03b1-11ed-920c-974bfa104448",
- "type" : "create_case",
+ "description" : "A case description.",
+ "title" : "Case title 1",
"created_by" : {
"full_name" : "full_name",
"profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
"email" : "email",
"username" : "elastic"
+ },
+ "version" : "WzUzMiwxXQ==",
+ "closed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "tags" : [ "tag-1" ],
+ "duration" : 120,
+ "updated_at" : "2000-01-23T04:56:07.000+00:00",
+ "updated_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
+ "external_service" : {
+ "external_title" : "external_title",
+ "pushed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "external_url" : "external_url",
+ "pushed_at" : "2000-01-23T04:56:07.000+00:00",
+ "connector_id" : "connector_id",
+ "external_id" : "external_id",
+ "connector_name" : "connector_name"
}
}
@@ -1171,18 +1944,18 @@ Any modifications made to this file will be overwritten.
Responses
200
Indicates a successful call.
-
+
case_response_properties
401
Authorization information is missing or invalid.
4xx_response
-
+
Up
-
get /s/{spaceId}/api/cases/{caseId}/alerts
-
Gets all alerts attached to a case. (getCaseAlerts)
-
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+
get /s/{spaceId}/api/cases/{caseId}/user_actions
+
Returns all user activity for a case. (getCaseActivity)
+
Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're seeking.
Path parameters
@@ -1200,7 +1973,7 @@ Any modifications made to this file will be overwritten.
Return type
@@ -1209,9 +1982,19 @@ Any modifications made to this file will be overwritten.
Example data
Content-Type: application/json
{
- "index" : "index",
- "id" : "id",
- "attached_at" : "2000-01-23T04:56:07.000+00:00"
+ "owner" : "cases",
+ "action_id" : "22fd3e30-03b1-11ed-920c-974bfa104448",
+ "case_id" : "22df07d0-03b1-11ed-920c-974bfa104448",
+ "action" : "create",
+ "created_at" : "2022-05-13T09:16:17.416Z",
+ "comment_id" : "578608d0-03b1-11ed-920c-974bfa104448",
+ "type" : "create_case",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ }
}
Produces
@@ -1230,22 +2013,18 @@ Any modifications made to this file will be overwritten.
4xx_response
-
+
Up
-
get /s/{spaceId}/api/cases/{caseId}/comments/{commentId}
-
Retrieves a comment from a case. (getCaseComment)
-
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.
+
get /api/cases/{caseId}/user_actions
+
Returns all user activity for a case in the default space. (getCaseActivityDefaultSpace)
+
Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're seeking.
Path parameters
caseId (required)
-
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
commentId (required)
-
-
Path Parameter — The identifier for the comment. To retrieve comment IDs, use the get case or find cases APIs. default: null
spaceId (required)
-
-
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
@@ -1255,7 +2034,7 @@ Any modifications made to this file will be overwritten.
Return type
@@ -1263,7 +2042,21 @@ Any modifications made to this file will be overwritten.
Example data
Content-Type: application/json
-
null
+
{
+ "owner" : "cases",
+ "action_id" : "22fd3e30-03b1-11ed-920c-974bfa104448",
+ "case_id" : "22df07d0-03b1-11ed-920c-974bfa104448",
+ "action" : "create",
+ "created_at" : "2022-05-13T09:16:17.416Z",
+ "comment_id" : "578608d0-03b1-11ed-920c-974bfa104448",
+ "type" : "create_case",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ }
+}
Produces
This API call produces the following media types according to the request header;
@@ -1275,22 +2068,24 @@ Any modifications made to this file will be overwritten.
Responses
200
Indicates a successful call.
-
getCaseComment_200_response
+
401
Authorization information is missing or invalid.
4xx_response
-
+
Up
-
get /s/{spaceId}/api/cases/configure
-
Retrieves external connection details, such as the closure type and default connector for cases. (getCaseConfiguration)
-
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case configuration.
+
get /s/{spaceId}/api/cases/{caseId}/alerts
+
Gets all alerts attached to a case. (getCaseAlerts)
+
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
Path parameters
-
spaceId (required)
+
caseId (required)
+
+
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
spaceId (required)
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
@@ -1298,17 +2093,11 @@ Any modifications made to this file will be overwritten.
-
Query parameters
-
-
owner (optional)
-
-
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
-
Return type
@@ -1317,40 +2106,9 @@ Any modifications made to this file will be overwritten.
Example data
Content-Type: application/json
{
- "closure_type" : "close-by-user",
- "owner" : "cases",
- "mappings" : [ {
- "action_type" : "overwrite",
- "source" : "title",
- "target" : "summary"
- }, {
- "action_type" : "overwrite",
- "source" : "title",
- "target" : "summary"
- } ],
- "connector" : {
- "name" : "none",
- "id" : "none",
- "fields" : "{}",
- "type" : ".none"
- },
- "updated_at" : "2022-06-01T19:58:48.169Z",
- "updated_by" : {
- "full_name" : "full_name",
- "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
- "email" : "email",
- "username" : "elastic"
- },
- "created_at" : "2022-06-01T17:07:17.767Z",
- "id" : "4a97a440-e1cd-11ec-be9b-9b1838238ee6",
- "error" : "error",
- "created_by" : {
- "full_name" : "full_name",
- "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
- "email" : "email",
- "username" : "elastic"
- },
- "version" : "WzIwNzMsMV0="
+ "index" : "index",
+ "id" : "id",
+ "attached_at" : "2000-01-23T04:56:07.000+00:00"
}
Produces
@@ -1369,34 +2127,28 @@ Any modifications made to this file will be overwritten.
4xx_response
-
+
Up
-
get /s/{spaceId}/api/cases/reporters
-
Returns information about the users who opened cases. (getCaseReporters)
-
You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases. The API returns information about the users as they existed at the time of the case creation, including their name, full name, and email address. If any of those details change thereafter or if a user is deleted, the information returned by this API is unchanged.
+
get /api/cases/{caseId}/alerts
+
Gets all alerts attached to a case in the default space. (getCaseAlertsDefaultSpace)
+
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
Path parameters
-
spaceId (required)
+
caseId (required)
-
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
-
Query parameters
-
-
owner (optional)
-
-
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
-
Return type
@@ -1405,10 +2157,9 @@ Any modifications made to this file will be overwritten.
Example data
Content-Type: application/json
{
- "full_name" : "full_name",
- "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
- "email" : "email",
- "username" : "elastic"
+ "index" : "index",
+ "id" : "id",
+ "attached_at" : "2000-01-23T04:56:07.000+00:00"
}
Produces
@@ -1427,16 +2178,20 @@ Any modifications made to this file will be overwritten.
4xx_response
-
+
Up
-
get /s/{spaceId}/api/cases/status
-
Returns the number of cases that are open, closed, and in progress. (getCaseStatus)
-
Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+
get /s/{spaceId}/api/cases/{caseId}/comments/{commentId}
+
Retrieves a comment from a case. (getCaseComment)
+
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.
Path parameters
-
spaceId (required)
+
caseId (required)
+
+
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
commentId (required)
+
+
Path Parameter — The identifier for the comment. To retrieve comment IDs, use the get case or find cases APIs. default: null
spaceId (required)
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
@@ -1444,17 +2199,11 @@ Any modifications made to this file will be overwritten.
-
Query parameters
-
-
owner (optional)
-
-
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
-
Return type
@@ -1462,11 +2211,7 @@ Any modifications made to this file will be overwritten.
Example data
Content-Type: application/json
-
{
- "count_in_progress_cases" : 6,
- "count_open_cases" : 1,
- "count_closed_cases" : 0
-}
+
null
Produces
This API call produces the following media types according to the request header;
@@ -1478,48 +2223,44 @@ Any modifications made to this file will be overwritten.
Responses
200
Indicates a successful call.
-
getCaseStatus_200_response
+
getCaseCommentDefaultSpace_200_response
401
Authorization information is missing or invalid.
4xx_response
-
+
Up
-
get /s/{spaceId}/api/cases/tags
-
Aggregates and returns a list of case tags. (getCaseTags)
-
You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+
get /api/cases/{caseId}/comments/{commentId}
+
Retrieves a comment from a case in the default space. (getCaseCommentDefaultSpace)
+
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.
Path parameters
-
spaceId (required)
+
caseId (required)
-
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
-
+
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
commentId (required)
+
Path Parameter — The identifier for the comment. To retrieve comment IDs, use the get case or find cases APIs. default: null
+
-
Query parameters
-
-
owner (optional)
-
Query Parameter — A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read. default: null
-
Return type
Example data
Content-Type: application/json
-
""
+
null
Produces
This API call produces the following media types according to the request header;
@@ -1531,24 +2272,22 @@ Any modifications made to this file will be overwritten.
Responses
200
Indicates a successful call.
-
+
getCaseCommentDefaultSpace_200_response
401
Authorization information is missing or invalid.
4xx_response
-
+
Up
-
get /s/{spaceId}/api/cases/alerts/{alertId}
-
Returns the cases associated with a specific alert. (getCasesByAlert)
-
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+
get /s/{spaceId}/api/cases/configure
+
Retrieves external connection details, such as the closure type and default connector for cases. (getCaseConfiguration)
+
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case configuration.
Path parameters
-
alertId (required)
-
-
Path Parameter — An identifier for the alert. default: null
spaceId (required)
+
spaceId (required)
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
@@ -1566,7 +2305,7 @@ Any modifications made to this file will be overwritten.
Return type
@@ -1574,10 +2313,1347 @@ Any modifications made to this file will be overwritten.
Example data
Content-Type: application/json
-
[ {
- "id" : "06116b80-e1c3-11ec-be9b-9b1838238ee6",
- "title" : "security_case"
-} ]
+
{
+ "closure_type" : "close-by-user",
+ "owner" : "cases",
+ "mappings" : [ {
+ "action_type" : "overwrite",
+ "source" : "title",
+ "target" : "summary"
+ }, {
+ "action_type" : "overwrite",
+ "source" : "title",
+ "target" : "summary"
+ } ],
+ "connector" : {
+ "name" : "none",
+ "id" : "none",
+ "fields" : "{}",
+ "type" : ".none"
+ },
+ "updated_at" : "2022-06-01T19:58:48.169Z",
+ "updated_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "created_at" : "2022-06-01T17:07:17.767Z",
+ "id" : "4a97a440-e1cd-11ec-be9b-9b1838238ee6",
+ "error" : "error",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "version" : "WzIwNzMsMV0="
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
get /api/cases/configure
+
Retrieves external connection details, such as the closure type and default connector for cases in the default space. (getCaseConfigurationDefaultSpace)
+
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case configuration.
+
+
+
+
+
+
Query parameters
+
+
owner (optional)
+
+
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "closure_type" : "close-by-user",
+ "owner" : "cases",
+ "mappings" : [ {
+ "action_type" : "overwrite",
+ "source" : "title",
+ "target" : "summary"
+ }, {
+ "action_type" : "overwrite",
+ "source" : "title",
+ "target" : "summary"
+ } ],
+ "connector" : {
+ "name" : "none",
+ "id" : "none",
+ "fields" : "{}",
+ "type" : ".none"
+ },
+ "updated_at" : "2022-06-01T19:58:48.169Z",
+ "updated_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "created_at" : "2022-06-01T17:07:17.767Z",
+ "id" : "4a97a440-e1cd-11ec-be9b-9b1838238ee6",
+ "error" : "error",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "version" : "WzIwNzMsMV0="
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
get /api/cases/{caseId}
+
Retrieves information about a case in the default space. (getCaseDefaultSpace)
+
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're seeking.
+
+
Path parameters
+
+
caseId (required)
+
+
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
+
+
+
+
+
+
Query parameters
+
+
includeComments (optional)
+
+
Query Parameter — Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned. default: true
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "owner" : "cases",
+ "totalComment" : 0,
+ "settings" : {
+ "syncAlerts" : true
+ },
+ "totalAlerts" : 0,
+ "closed_at" : "2000-01-23T04:56:07.000+00:00",
+ "comments" : [ null, null ],
+ "assignees" : [ {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ }, {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ } ],
+ "created_at" : "2022-05-13T09:16:17.416Z",
+ "description" : "A case description.",
+ "title" : "Case title 1",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "version" : "WzUzMiwxXQ==",
+ "closed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "tags" : [ "tag-1" ],
+ "duration" : 120,
+ "updated_at" : "2000-01-23T04:56:07.000+00:00",
+ "updated_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
+ "external_service" : {
+ "external_title" : "external_title",
+ "pushed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "external_url" : "external_url",
+ "pushed_at" : "2000-01-23T04:56:07.000+00:00",
+ "connector_id" : "connector_id",
+ "external_id" : "external_id",
+ "connector_name" : "connector_name"
+ }
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
case_response_properties
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
get /s/{spaceId}/api/cases/reporters
+
Returns information about the users who opened cases. (getCaseReporters)
+
You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases. The API returns information about the users as they existed at the time of the case creation, including their name, full name, and email address. If any of those details change thereafter or if a user is deleted, the information returned by this API is unchanged.
+
+
Path parameters
+
+
spaceId (required)
+
+
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
+
+
+
+
+
Query parameters
+
+
owner (optional)
+
+
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
get /api/cases/reporters
+
Returns information about the users who opened cases in the default space. (getCaseReportersDefaultSpace)
+
You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases. The API returns information about the users as they existed at the time of the case creation, including their name, full name, and email address. If any of those details change thereafter or if a user is deleted, the information returned by this API is unchanged.
+
+
+
+
+
+
Query parameters
+
+
owner (optional)
+
+
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
get /s/{spaceId}/api/cases/status
+
Returns the number of cases that are open, closed, and in progress. (getCaseStatus)
+
Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+
+
Path parameters
+
+
spaceId (required)
+
+
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
+
+
+
+
+
Query parameters
+
+
owner (optional)
+
+
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "count_in_progress_cases" : 6,
+ "count_open_cases" : 1,
+ "count_closed_cases" : 0
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
getCaseStatusDefaultSpace_200_response
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
get /api/cases/status
+
Returns the number of cases that are open, closed, and in progress in the default space. (getCaseStatusDefaultSpace)
+
Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+
+
+
+
+
+
Query parameters
+
+
owner (optional)
+
+
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "count_in_progress_cases" : 6,
+ "count_open_cases" : 1,
+ "count_closed_cases" : 0
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
getCaseStatusDefaultSpace_200_response
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
get /s/{spaceId}/api/cases/tags
+
Aggregates and returns a list of case tags. (getCaseTags)
+
You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+
+
Path parameters
+
+
spaceId (required)
+
+
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
+
+
+
+
+
Query parameters
+
+
owner (optional)
+
+
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
+
+
+
+
Return type
+
+
+ array[String]
+
+
+
+
+
Example data
+
Content-Type: application/json
+
""
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
get /api/cases/tags
+
Aggregates and returns a list of case tags in the default space. (getCaseTagsDefaultSpace)
+
You must have read privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+
+
+
+
+
+
Query parameters
+
+
owner (optional)
+
+
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
+
+
+
+
Return type
+
+
+ array[String]
+
+
+
+
+
Example data
+
Content-Type: application/json
+
""
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
get /s/{spaceId}/api/cases/alerts/{alertId}
+
Returns the cases associated with a specific alert. (getCasesByAlert)
+
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+
+
Path parameters
+
+
alertId (required)
+
+
Path Parameter — An identifier for the alert. default: null
spaceId (required)
+
+
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
+
+
+
+
+
Query parameters
+
+
owner (optional)
+
+
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
[ {
+ "id" : "06116b80-e1c3-11ec-be9b-9b1838238ee6",
+ "title" : "security_case"
+} ]
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
get /api/cases/alerts/{alertId}
+
Returns the cases associated with a specific alert in the default space. (getCasesByAlertDefaultSpace)
+
You must have read
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+
+
Path parameters
+
+
alertId (required)
+
+
Path Parameter — An identifier for the alert. default: null
+
+
+
+
+
+
Query parameters
+
+
owner (optional)
+
+
Query Parameter — A filter to limit the response to a specific set of applications. If this parameter is omitted, the response contains information about all the cases that the user has access to read. default: null
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
[ {
+ "id" : "06116b80-e1c3-11ec-be9b-9b1838238ee6",
+ "title" : "security_case"
+} ]
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
post /s/{spaceId}/api/cases/{caseId}/connector/{connectorId}/_push
+
Pushes a case to an external service. (pushCase)
+
You must have all
privileges for the Actions and Connectors feature in the Management section of the Kibana feature privileges. You must also have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're pushing.
+
+
Path parameters
+
+
caseId (required)
+
+
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
connectorId (required)
+
+
Path Parameter — An identifier for the connector. To retrieve connector IDs, use the find connectors API. default: null
spaceId (required)
+
+
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
+
+
Consumes
+ This API call consumes the following media types via the request header:
+
+
+
Request body
+
+
+
+
Body Parameter —
+
+
+
+
Request headers
+
+
kbn-xsrf (required)
+
+
Header Parameter — Cross-site request forgery protection default: null
+
+
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "owner" : "cases",
+ "totalComment" : 0,
+ "settings" : {
+ "syncAlerts" : true
+ },
+ "totalAlerts" : 0,
+ "closed_at" : "2000-01-23T04:56:07.000+00:00",
+ "comments" : [ null, null ],
+ "assignees" : [ {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ }, {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ } ],
+ "created_at" : "2022-05-13T09:16:17.416Z",
+ "description" : "A case description.",
+ "title" : "Case title 1",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "version" : "WzUzMiwxXQ==",
+ "closed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "tags" : [ "tag-1" ],
+ "duration" : 120,
+ "updated_at" : "2000-01-23T04:56:07.000+00:00",
+ "updated_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
+ "external_service" : {
+ "external_title" : "external_title",
+ "pushed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "external_url" : "external_url",
+ "pushed_at" : "2000-01-23T04:56:07.000+00:00",
+ "connector_id" : "connector_id",
+ "external_id" : "external_id",
+ "connector_name" : "connector_name"
+ }
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
case_response_properties
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
post /api/cases/{caseId}/connector/{connectorId}/_push
+
Pushes a case in the default space to an external service. (pushCaseDefaultSpace)
+
You must have all
privileges for the Actions and Connectors feature in the Management section of the Kibana feature privileges. You must also have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're pushing.
+
+
Path parameters
+
+
caseId (required)
+
+
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
connectorId (required)
+
+
Path Parameter — An identifier for the connector. To retrieve connector IDs, use the find connectors API. default: null
+
+
+
Consumes
+ This API call consumes the following media types via the request header:
+
+
+
Request body
+
+
+
+
Body Parameter —
+
+
+
+
Request headers
+
+
kbn-xsrf (required)
+
+
Header Parameter — Cross-site request forgery protection default: null
+
+
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "owner" : "cases",
+ "totalComment" : 0,
+ "settings" : {
+ "syncAlerts" : true
+ },
+ "totalAlerts" : 0,
+ "closed_at" : "2000-01-23T04:56:07.000+00:00",
+ "comments" : [ null, null ],
+ "assignees" : [ {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ }, {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ } ],
+ "created_at" : "2022-05-13T09:16:17.416Z",
+ "description" : "A case description.",
+ "title" : "Case title 1",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "version" : "WzUzMiwxXQ==",
+ "closed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "tags" : [ "tag-1" ],
+ "duration" : 120,
+ "updated_at" : "2000-01-23T04:56:07.000+00:00",
+ "updated_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
+ "external_service" : {
+ "external_title" : "external_title",
+ "pushed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "external_url" : "external_url",
+ "pushed_at" : "2000-01-23T04:56:07.000+00:00",
+ "connector_id" : "connector_id",
+ "external_id" : "external_id",
+ "connector_name" : "connector_name"
+ }
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
case_response_properties
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
post /s/{spaceId}/api/cases/configure
+
Sets external connection details, such as the closure type and default connector for cases. (setCaseConfiguration)
+
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API. If you set a default connector, it is automatically selected when you create cases in Kibana. If you use the create case API, however, you must still specify all of the connector details.
+
+
Path parameters
+
+
spaceId (required)
+
+
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
+
+
Consumes
+ This API call consumes the following media types via the request header:
+
+
+
Request body
+
+
+
+
Body Parameter —
+
+
+
+
Request headers
+
+
kbn-xsrf (required)
+
+
Header Parameter — Cross-site request forgery protection default: null
+
+
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "closure_type" : "close-by-user",
+ "owner" : "cases",
+ "mappings" : [ {
+ "action_type" : "overwrite",
+ "source" : "title",
+ "target" : "summary"
+ }, {
+ "action_type" : "overwrite",
+ "source" : "title",
+ "target" : "summary"
+ } ],
+ "connector" : {
+ "name" : "none",
+ "id" : "none",
+ "fields" : "{}",
+ "type" : ".none"
+ },
+ "updated_at" : "2022-06-01T19:58:48.169Z",
+ "updated_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "created_at" : "2022-06-01T17:07:17.767Z",
+ "id" : "4a97a440-e1cd-11ec-be9b-9b1838238ee6",
+ "error" : "error",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "version" : "WzIwNzMsMV0="
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
getCaseConfigurationDefaultSpace_200_response_inner
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
post /api/cases/configure
+
Sets external connection details, such as the closure type and default connector for cases in the default space. (setCaseConfigurationDefaultSpace)
+
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API. If you set a default connector, it is automatically selected when you create cases in Kibana. If you use the create case API, however, you must still specify all of the connector details.
+
+
+
Consumes
+ This API call consumes the following media types via the request header:
+
+
+
Request body
+
+
+
+
Body Parameter —
+
+
+
+
Request headers
+
+
kbn-xsrf (required)
+
+
Header Parameter — Cross-site request forgery protection default: null
+
+
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "closure_type" : "close-by-user",
+ "owner" : "cases",
+ "mappings" : [ {
+ "action_type" : "overwrite",
+ "source" : "title",
+ "target" : "summary"
+ }, {
+ "action_type" : "overwrite",
+ "source" : "title",
+ "target" : "summary"
+ } ],
+ "connector" : {
+ "name" : "none",
+ "id" : "none",
+ "fields" : "{}",
+ "type" : ".none"
+ },
+ "updated_at" : "2022-06-01T19:58:48.169Z",
+ "updated_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "created_at" : "2022-06-01T17:07:17.767Z",
+ "id" : "4a97a440-e1cd-11ec-be9b-9b1838238ee6",
+ "error" : "error",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "version" : "WzIwNzMsMV0="
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
getCaseConfigurationDefaultSpace_200_response_inner
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
patch /s/{spaceId}/api/cases
+
Updates one or more cases. (updateCase)
+
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're updating.
+
+
Path parameters
+
+
spaceId (required)
+
+
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
+
+
Consumes
+ This API call consumes the following media types via the request header:
+
+
+
Request body
+
+
+
+
Body Parameter —
+
+
+
+
Request headers
+
+
kbn-xsrf (required)
+
+
Header Parameter — Cross-site request forgery protection default: null
+
+
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "owner" : "cases",
+ "totalComment" : 0,
+ "settings" : {
+ "syncAlerts" : true
+ },
+ "totalAlerts" : 0,
+ "closed_at" : "2000-01-23T04:56:07.000+00:00",
+ "comments" : [ null, null ],
+ "assignees" : [ {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ }, {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ } ],
+ "created_at" : "2022-05-13T09:16:17.416Z",
+ "description" : "A case description.",
+ "title" : "Case title 1",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "version" : "WzUzMiwxXQ==",
+ "closed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "tags" : [ "tag-1" ],
+ "duration" : 120,
+ "updated_at" : "2000-01-23T04:56:07.000+00:00",
+ "updated_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
+ "external_service" : {
+ "external_title" : "external_title",
+ "pushed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "external_url" : "external_url",
+ "pushed_at" : "2000-01-23T04:56:07.000+00:00",
+ "connector_id" : "connector_id",
+ "external_id" : "external_id",
+ "connector_name" : "connector_name"
+ }
+}
+
+
Produces
+ This API call produces the following media types according to the request header;
+ the media type will be conveyed by the response header.
+
+
+
Responses
+
200
+ Indicates a successful call.
+
+
401
+ Authorization information is missing or invalid.
+
4xx_response
+
+
+
+
+
Up
+
patch /s/{spaceId}/api/cases/{caseId}/comments
+
Updates a comment or alert in a case. (updateCaseComment)
+
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.
+
+
Path parameters
+
+
caseId (required)
+
+
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
spaceId (required)
+
+
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
+
+
Consumes
+ This API call consumes the following media types via the request header:
+
+
+
Request body
+
+
+
+
Body Parameter —
+
+
+
+
Request headers
+
+
kbn-xsrf (required)
+
+
Header Parameter — Cross-site request forgery protection default: null
+
+
+
+
+
+
Return type
+
+
+
+
+
Example data
+
Content-Type: application/json
+
{
+ "owner" : "cases",
+ "totalComment" : 0,
+ "settings" : {
+ "syncAlerts" : true
+ },
+ "totalAlerts" : 0,
+ "closed_at" : "2000-01-23T04:56:07.000+00:00",
+ "comments" : [ null, null ],
+ "assignees" : [ {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ }, {
+ "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ } ],
+ "created_at" : "2022-05-13T09:16:17.416Z",
+ "description" : "A case description.",
+ "title" : "Case title 1",
+ "created_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "version" : "WzUzMiwxXQ==",
+ "closed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "tags" : [ "tag-1" ],
+ "duration" : 120,
+ "updated_at" : "2000-01-23T04:56:07.000+00:00",
+ "updated_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
+ "external_service" : {
+ "external_title" : "external_title",
+ "pushed_by" : {
+ "full_name" : "full_name",
+ "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
+ "email" : "email",
+ "username" : "elastic"
+ },
+ "external_url" : "external_url",
+ "pushed_at" : "2000-01-23T04:56:07.000+00:00",
+ "connector_id" : "connector_id",
+ "external_id" : "external_id",
+ "connector_name" : "connector_name"
+ }
+}
Produces
This API call produces the following media types according to the request header;
@@ -1589,28 +3665,24 @@ Any modifications made to this file will be overwritten.
Responses
200
Indicates a successful call.
-
+
case_response_properties
401
Authorization information is missing or invalid.
4xx_response
-
+
Up
-
post /s/{spaceId}/api/cases/{caseId}/connector/{connectorId}/_push
-
Pushes a case to an external service. (pushCase)
-
You must have all
privileges for the Actions and Connectors feature in the Management section of the Kibana feature privileges. You must also have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're pushing.
+
patch /api/cases/{caseId}/comments
+
Updates a comment or alert in a case in the default space. (updateCaseCommentDefaultSpace)
+
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.
Path parameters
caseId (required)
-
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
connectorId (required)
-
-
Path Parameter — An identifier for the connector. To retrieve connector IDs, use the find connectors API. default: null
spaceId (required)
-
-
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
Consumes
@@ -1621,7 +3693,7 @@ Any modifications made to this file will be overwritten.
Request body
-
+
Body Parameter —
@@ -1719,16 +3791,18 @@ Any modifications made to this file will be overwritten.
4xx_response
-
+
Up
-
post /s/{spaceId}/api/cases/configure
-
Sets external connection details, such as the closure type and default connector for cases. (setCaseConfiguration)
-
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API. If you set a default connector, it is automatically selected when you create cases in Kibana. If you use the create case API, however, you must still specify all of the connector details.
+
patch /s/{spaceId}/api/cases/configure/{configurationId}
+
Updates external connection details, such as the closure type and default connector for cases. (updateCaseConfiguration)
+
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API.
Path parameters
-
spaceId (required)
+
configurationId (required)
+
+
Path Parameter — An identifier for the configuration. default: null
spaceId (required)
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
@@ -1741,7 +3815,7 @@ Any modifications made to this file will be overwritten.
Request body
-
+
Body Parameter —
@@ -1759,7 +3833,7 @@ Any modifications made to this file will be overwritten.
Return type
@@ -1814,24 +3888,24 @@ Any modifications made to this file will be overwritten.
Responses
200
Indicates a successful call.
-
getCaseConfiguration_200_response_inner
+
401
Authorization information is missing or invalid.
4xx_response
-
+
Up
-
patch /s/{spaceId}/api/cases
-
Updates one or more cases. (updateCase)
-
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're updating.
+
patch /api/cases/configure/{configurationId}
+
Updates external connection details, such as the closure type and default connector for cases in the default space. (updateCaseConfigurationDefaultSpace)
+
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API.
Path parameters
-
spaceId (required)
+
configurationId (required)
-
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
+
Path Parameter — An identifier for the configuration. default: null
Consumes
@@ -1842,7 +3916,7 @@ Any modifications made to this file will be overwritten.
Request body
-
+
Body Parameter —
@@ -1860,7 +3934,7 @@ Any modifications made to this file will be overwritten.
Return type
@@ -1869,59 +3943,40 @@ Any modifications made to this file will be overwritten.
Example data
Content-Type: application/json
{
+ "closure_type" : "close-by-user",
"owner" : "cases",
- "totalComment" : 0,
- "settings" : {
- "syncAlerts" : true
- },
- "totalAlerts" : 0,
- "closed_at" : "2000-01-23T04:56:07.000+00:00",
- "comments" : [ null, null ],
- "assignees" : [ {
- "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ "mappings" : [ {
+ "action_type" : "overwrite",
+ "source" : "title",
+ "target" : "summary"
}, {
- "uid" : "u_0wpfV1MqYDaXzLtRVY-gLMrddKDEmfz51Fszhj7hWC8_0"
+ "action_type" : "overwrite",
+ "source" : "title",
+ "target" : "summary"
} ],
- "created_at" : "2022-05-13T09:16:17.416Z",
- "description" : "A case description.",
- "title" : "Case title 1",
- "created_by" : {
- "full_name" : "full_name",
- "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
- "email" : "email",
- "username" : "elastic"
+ "connector" : {
+ "name" : "none",
+ "id" : "none",
+ "fields" : "{}",
+ "type" : ".none"
},
- "version" : "WzUzMiwxXQ==",
- "closed_by" : {
+ "updated_at" : "2022-06-01T19:58:48.169Z",
+ "updated_by" : {
"full_name" : "full_name",
"profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
"email" : "email",
"username" : "elastic"
},
- "tags" : [ "tag-1" ],
- "duration" : 120,
- "updated_at" : "2000-01-23T04:56:07.000+00:00",
- "updated_by" : {
+ "created_at" : "2022-06-01T17:07:17.767Z",
+ "id" : "4a97a440-e1cd-11ec-be9b-9b1838238ee6",
+ "error" : "error",
+ "created_by" : {
"full_name" : "full_name",
"profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
"email" : "email",
"username" : "elastic"
},
- "id" : "66b9aa00-94fa-11ea-9f74-e7e108796192",
- "external_service" : {
- "external_title" : "external_title",
- "pushed_by" : {
- "full_name" : "full_name",
- "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
- "email" : "email",
- "username" : "elastic"
- },
- "external_url" : "external_url",
- "pushed_at" : "2000-01-23T04:56:07.000+00:00",
- "connector_id" : "connector_id",
- "external_id" : "external_id",
- "connector_name" : "connector_name"
- }
+ "version" : "WzIwNzMsMV0="
}
Produces
@@ -1940,21 +3995,13 @@ Any modifications made to this file will be overwritten.
4xx_response
-
+
Up
-
patch /s/{spaceId}/api/cases/{caseId}/comments
-
Updates a comment or alert in a case. (updateCaseComment)
-
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.
-
-
Path parameters
-
-
caseId (required)
-
-
Path Parameter — The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null
spaceId (required)
+
patch /api/cases
+
Updates one or more cases in the default space. (updateCaseDefaultSpace)
+
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case you're updating.
-
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
-
Consumes
This API call consumes the following media types via the request header:
@@ -1964,7 +4011,7 @@ Any modifications made to this file will be overwritten.
Request body
-
+
Body Parameter —
@@ -1982,7 +4029,7 @@ Any modifications made to this file will be overwritten.
Return type
@@ -2053,109 +4100,6 @@ Any modifications made to this file will be overwritten.
application/json
-
Responses
-
200
- Indicates a successful call.
-
case_response_properties
-
401
- Authorization information is missing or invalid.
-
4xx_response
-
-
-
-
-
Up
-
patch /s/{spaceId}/api/cases/configure/{configurationId}
-
Updates external connection details, such as the closure type and default connector for cases. (updateCaseConfiguration)
-
You must have all
privileges for the Cases feature in the Management, Observability, or Security section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API.
-
-
Path parameters
-
-
configurationId (required)
-
-
Path Parameter — An identifier for the configuration. default: null
spaceId (required)
-
-
Path Parameter — An identifier for the space. If /s/
and the identifier are omitted from the path, the default space is used. default: null
-
-
-
Consumes
- This API call consumes the following media types via the request header:
-
-
-
Request body
-
-
-
-
Body Parameter —
-
-
-
-
Request headers
-
-
kbn-xsrf (required)
-
-
Header Parameter — Cross-site request forgery protection default: null
-
-
-
-
-
-
Return type
-
-
-
-
-
Example data
-
Content-Type: application/json
-
{
- "closure_type" : "close-by-user",
- "owner" : "cases",
- "mappings" : [ {
- "action_type" : "overwrite",
- "source" : "title",
- "target" : "summary"
- }, {
- "action_type" : "overwrite",
- "source" : "title",
- "target" : "summary"
- } ],
- "connector" : {
- "name" : "none",
- "id" : "none",
- "fields" : "{}",
- "type" : ".none"
- },
- "updated_at" : "2022-06-01T19:58:48.169Z",
- "updated_by" : {
- "full_name" : "full_name",
- "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
- "email" : "email",
- "username" : "elastic"
- },
- "created_at" : "2022-06-01T17:07:17.767Z",
- "id" : "4a97a440-e1cd-11ec-be9b-9b1838238ee6",
- "error" : "error",
- "created_by" : {
- "full_name" : "full_name",
- "profile_uid" : "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0",
- "email" : "email",
- "username" : "elastic"
- },
- "version" : "WzIwNzMsMV0="
-}
-
-
Produces
- This API call produces the following media types according to the request header;
- the media type will be conveyed by the response header.
-
-
Responses
200
Indicates a successful call.
@@ -2207,21 +4151,22 @@ Any modifications made to this file will be overwritten.
create_case_request
- Create case request
create_case_request_connector
-
external_service
-
-
findCaseActivity_200_response
-
-
findCaseConnectors_200_response_inner
-
-
findCaseConnectors_200_response_inner_config
-
-
findCases_200_response
-
-
findCases_assignees_parameter
-
-
findCases_owner_parameter
-
-
findCases_searchFields_parameter
-
-
getCaseComment_200_response
-
-
getCaseConfiguration_200_response_inner
-
-
getCaseConfiguration_200_response_inner_connector
-
-
getCaseConfiguration_200_response_inner_created_by
-
-
getCaseConfiguration_200_response_inner_mappings_inner
-
-
getCaseConfiguration_200_response_inner_updated_by
-
-
getCaseStatus_200_response
-
-
getCasesByAlert_200_response_inner
-
+
findCaseActivityDefaultSpace_200_response
-
+
findCaseConnectorsDefaultSpace_200_response_inner
-
+
findCaseConnectorsDefaultSpace_200_response_inner_config
-
+
findCasesDefaultSpace_200_response
-
+
findCasesDefaultSpace_assignees_parameter
-
+
findCasesDefaultSpace_category_parameter
-
+
findCasesDefaultSpace_owner_parameter
-
+
findCasesDefaultSpace_searchFields_parameter
-
+
getCaseCommentDefaultSpace_200_response
-
+
getCaseConfigurationDefaultSpace_200_response_inner
-
+
getCaseConfigurationDefaultSpace_200_response_inner_connector
-
+
getCaseConfigurationDefaultSpace_200_response_inner_created_by
-
+
getCaseConfigurationDefaultSpace_200_response_inner_mappings_inner
-
+
getCaseConfigurationDefaultSpace_200_response_inner_updated_by
-
+
getCaseStatusDefaultSpace_200_response
-
+
getCasesByAlertDefaultSpace_200_response_inner
-
owners
-
payload_alert_comment
-
payload_alert_comment_comment
-
@@ -2242,16 +4187,16 @@ Any modifications made to this file will be overwritten.
payload_user_comment
-
payload_user_comment_comment
-
rule
- Alerting rule
-
search_fields
-
-
setCaseConfiguration_request
-
-
setCaseConfiguration_request_connector
-
-
setCaseConfiguration_request_settings
-
+
searchFieldsType
-
+
set_case_configuration_request
- Set case configuration request
+
set_case_configuration_request_connector
-
+
set_case_configuration_request_settings
-
settings
-
severity_property
-
status
-
-
updateCaseConfiguration_request
-
update_alert_comment_request_properties
- Update case comment request properties for alerts
update_case_comment_request
- Update case comment request
+
update_case_configuration_request
- Update case configuration request
update_case_request
- Update case request
update_case_request_cases_inner
-
update_user_comment_request_properties
- Update case comment request properties for user comments
@@ -2361,18 +4306,18 @@ Any modifications made to this file will be overwritten.
alertId (optional)
created_at (optional)
-
created_by (optional)
+
created_by (optional)
id (optional)
index (optional)
owner (optional)
pushed_at (optional)
-
pushed_by (optional)
+
pushed_by (optional)
rule (optional)
type
alert
updated_at (optional)
-
updated_by (optional)
+
updated_by (optional)
version (optional)
@@ -2660,11 +4605,11 @@ Any modifications made to this file will be overwritten.
external_title (optional)
external_url (optional)
pushed_at (optional)
-
pushed_by (optional)
+
pushed_by (optional)
-
+
page (optional)
@@ -2674,11 +4619,11 @@ Any modifications made to this file will be overwritten.
-
+
actionTypeId (optional)
-
config (optional)
+
config (optional)
id (optional)
isDeprecated (optional)
isMissingSecrets (optional)
@@ -2688,7 +4633,7 @@ Any modifications made to this file will be overwritten.
-
+
apiUrl (optional)
@@ -2696,7 +4641,7 @@ Any modifications made to this file will be overwritten.
-
+
cases (optional)
@@ -2709,25 +4654,31 @@ Any modifications made to this file will be overwritten.
+
-
+
alertId (optional)
@@ -2749,24 +4700,24 @@ Any modifications made to this file will be overwritten.
-
+
closure_type (optional)
-
connector (optional)
+
connector (optional)
created_at (optional)
-
created_by (optional)
+
created_by (optional)
error (optional)
id (optional)
-
mappings (optional)
+
mappings (optional)
owner (optional)
updated_at (optional)
-
updated_by (optional)
+
updated_by (optional)
version (optional)
-
+
fields (optional)
Object The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to
null
.
@@ -2776,7 +4727,7 @@ Any modifications made to this file will be overwritten.
-
+
email (optional)
@@ -2786,7 +4737,7 @@ Any modifications made to this file will be overwritten.
-
+
action_type (optional)
@@ -2795,7 +4746,7 @@ Any modifications made to this file will be overwritten.
-
+
email (optional)
@@ -2805,7 +4756,7 @@ Any modifications made to this file will be overwritten.
-
+
count_closed_cases (optional)
@@ -2814,7 +4765,7 @@ Any modifications made to this file will be overwritten.
-
+
id (optional)
@@ -2995,23 +4946,23 @@ Any modifications made to this file will be overwritten.
-
+
The fields to perform the simple_query_string
parsed query against.
-
-
+
+
External connection details, such as the closure type and default connector for cases.
closure_type
-
connector
+
connector
owner
-
settings (optional)
+
settings (optional)
-
+
An object that contains the connector configuration.
fields
Object The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to
null
.
@@ -3021,7 +4972,7 @@ Any modifications made to this file will be overwritten.
-
+
An object that contains the case settings.
syncAlerts
Boolean Turns alert syncing on or off.
@@ -3046,15 +4997,6 @@ Any modifications made to this file will be overwritten.
-
-
-
-
-
closure_type (optional)
-
connector (optional)
-
version
String The version of the connector. To retrieve the version value, use the get configuration API.
-
-
Defines properties for case comment requests when type is alert.
@@ -3086,6 +5028,15 @@ Any modifications made to this file will be overwritten.
comment
String The new comment. It is required only when
type
is
user
.
+
+
+
External connection details, such as the closure type and default connector for cases.
+
+
closure_type (optional)
+
connector (optional)
+
version
String The version of the connector. To retrieve the version value, use the get configuration API.
+
+
The update case API request body varies depending on the type of connector.
diff --git a/docs/osquery/images/enter-query.png b/docs/osquery/images/enter-query.png
index 189edc551c6b1..83f6eb08e60d7 100644
Binary files a/docs/osquery/images/enter-query.png and b/docs/osquery/images/enter-query.png differ
diff --git a/fleet_packages.json b/fleet_packages.json
index d32540601fa18..94200989f5205 100644
--- a/fleet_packages.json
+++ b/fleet_packages.json
@@ -34,7 +34,7 @@
},
{
"name": "endpoint",
- "version": "8.9.0"
+ "version": "8.9.1"
},
{
"name": "fleet_server",
@@ -58,6 +58,6 @@
},
{
"name": "security_detection_engine",
- "version": "8.8.4"
+ "version": "8.8.5"
}
]
\ No newline at end of file
diff --git a/packages/content-management/table_list_view/src/table_list_view.tsx b/packages/content-management/table_list_view/src/table_list_view.tsx
index 59fbe67a4a4c5..71b08f82174bb 100644
--- a/packages/content-management/table_list_view/src/table_list_view.tsx
+++ b/packages/content-management/table_list_view/src/table_list_view.tsx
@@ -7,7 +7,7 @@
*/
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
-import React, { ReactNode, useState } from 'react';
+import React, { ReactNode, useCallback, useState } from 'react';
import {
TableListViewTable,
type TableListViewTableProps,
@@ -81,6 +81,12 @@ export const TableListView =
({
const [hasInitialFetchReturned, setHasInitialFetchReturned] = useState(false);
const [pageDataTestSubject, setPageDataTestSubject] = useState();
+ const onFetchSuccess = useCallback(() => {
+ if (!hasInitialFetchReturned) {
+ setHasInitialFetchReturned(true);
+ }
+ }, [hasInitialFetchReturned]);
+
return (
({
contentEditor={contentEditor}
titleColumnName={titleColumnName}
withoutPageTemplateWrapper={withoutPageTemplateWrapper}
- onFetchSuccess={() => {
- if (!hasInitialFetchReturned) {
- setHasInitialFetchReturned(true);
- }
- }}
+ onFetchSuccess={onFetchSuccess}
setPageDataTestSubject={setPageDataTestSubject}
/>
diff --git a/packages/content-management/table_list_view_table/src/table_list_view.test.tsx b/packages/content-management/table_list_view_table/src/table_list_view.test.tsx
index f03ab50c5234b..408d22bd48ecf 100644
--- a/packages/content-management/table_list_view_table/src/table_list_view.test.tsx
+++ b/packages/content-management/table_list_view_table/src/table_list_view.test.tsx
@@ -1197,10 +1197,23 @@ describe('TableList', () => {
}
);
- it('refreshes the list when the bouncer changes', async () => {
+ it('refreshes the list when "refreshListBouncer" changes', async () => {
let testBed: TestBed;
- const findItems = jest.fn().mockResolvedValue({ total: 0, hits: [] });
+ const originalHits: UserContentCommonSchema[] = [
+ {
+ id: `item`,
+ type: 'dashboard',
+ updatedAt: 'original timestamp',
+ attributes: {
+ title: `Original title`,
+ },
+ references: [],
+ },
+ ];
+ const findItems = jest
+ .fn()
+ .mockResolvedValue({ total: originalHits.length, hits: originalHits });
await act(async () => {
testBed = setup({ findItems });
@@ -1215,7 +1228,7 @@ describe('TableList', () => {
{
id: `item`,
type: 'dashboard',
- updatedAt: 'some date',
+ updatedAt: 'updated timestamp',
attributes: {
title: `Updated title`,
},
diff --git a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx
index a0d544de5687e..2b0307c5d6f8e 100644
--- a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx
+++ b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx
@@ -369,7 +369,7 @@ function TableListViewTableComp({
} = state;
const hasQuery = searchQuery.text !== '';
- const hasNoItems = !isFetchingItems && items.length === 0 && !hasQuery;
+ const hasNoItems = hasInitialFetchReturned && items.length === 0 && !hasQuery;
const showFetchError = Boolean(fetchError);
const showLimitError = !showFetchError && totalItems > listingLimit;
@@ -411,10 +411,6 @@ function TableListViewTableComp({
}
}, [searchQueryParser, searchQuery.text, findItems, onFetchSuccess]);
- useEffect(() => {
- fetchItems();
- }, [fetchItems, refreshListBouncer]);
-
const updateQuery = useCallback(
(query: Query) => {
if (urlStateEnabled) {
@@ -800,7 +796,17 @@ function TableListViewTableComp({
// ------------
// Effects
// ------------
- useDebounce(fetchItems, 300, [fetchItems]);
+ useDebounce(
+ () => {
+ // Do not call fetchItems on dependency changes when initial fetch does not load any items
+ // to avoid flashing between empty table and no items view
+ if (!hasNoItems) {
+ fetchItems();
+ }
+ },
+ 300,
+ [fetchItems, refreshListBouncer]
+ );
useEffect(() => {
if (!urlStateEnabled) {
diff --git a/packages/kbn-apm-synthtrace-client/index.ts b/packages/kbn-apm-synthtrace-client/index.ts
index 1868cb188582e..04bef11e3f657 100644
--- a/packages/kbn-apm-synthtrace-client/index.ts
+++ b/packages/kbn-apm-synthtrace-client/index.ts
@@ -19,15 +19,17 @@ export type {
OSInfo,
} from './src/lib/apm/mobile_device';
export { httpExitSpan } from './src/lib/apm/span';
+export { type AssetDocument } from './src/lib/assets';
export { DistributedTrace } from './src/lib/dsl/distributed_trace_client';
export { serviceMap } from './src/lib/dsl/service_map';
export type { Fields } from './src/lib/entity';
+export { infra, type InfraDocument } from './src/lib/infra';
+export { parseInterval } from './src/lib/interval';
+export { monitoring, type MonitoringDocument } from './src/lib/monitoring';
export type { Serializable } from './src/lib/serializable';
export { timerange } from './src/lib/timerange';
export type { Timerange } from './src/lib/timerange';
+export { dedot } from './src/lib/utils/dedot';
export { generateLongId, generateShortId } from './src/lib/utils/generate_id';
export { appendHash, hashKeysOf } from './src/lib/utils/hash';
-export { dedot } from './src/lib/utils/dedot';
export type { ESDocumentWithOperation, SynthtraceESAction, SynthtraceGenerator } from './src/types';
-
-export { parseInterval } from './src/lib/interval';
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/apm/service.ts b/packages/kbn-apm-synthtrace-client/src/lib/apm/service.ts
index 1925c0cdcfd13..668834e73106e 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/apm/service.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/apm/service.ts
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+import { ServiceAsset } from '../assets';
import { Entity } from '../entity';
import { ApmFields } from './apm_fields';
import { Instance } from './instance';
@@ -18,6 +19,15 @@ export class Service extends Entity {
'host.name': instanceName,
});
}
+
+ asset() {
+ return new ServiceAsset({
+ 'asset.kind': 'service',
+ 'asset.id': this.fields['service.name']!,
+ 'asset.name': this.fields['service.name'],
+ 'asset.ean': `service:${this.fields['service.name']}`,
+ });
+ }
}
export function service(name: string, environment: string, agentName: string): Service;
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/assets/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/assets/index.ts
new file mode 100644
index 0000000000000..f449dd75972b7
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/assets/index.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+// eslint-disable-next-line max-classes-per-file
+import { Fields } from '../entity';
+import { Serializable } from '../serializable';
+
+// Can I pull in types from asset-manager here?
+type AssetKind = 'host' | 'pod' | 'container' | 'service';
+
+export interface AssetKindDocument extends Fields {
+ 'asset.kind': T;
+ 'asset.ean': string;
+ 'asset.id': string;
+ 'asset.name'?: string;
+ 'asset.parents'?: string[];
+ 'asset.children'?: string[];
+ 'asset.references'?: string[];
+}
+
+// What is the best way to tie up relationships?
+// With these setters we can go both ways but the entities might be able to produce
+// pre-linked assets as well
+class Asset extends Serializable> {
+ parents(parents: string[]) {
+ this.fields['asset.parents'] = parents;
+ }
+
+ children(children: string[]) {
+ this.fields['asset.children'] = children;
+ }
+
+ references(references: string[]) {
+ this.fields['asset.references'] = references;
+ }
+}
+
+export class HostAsset extends Asset<'host'> {}
+
+export class PodAsset extends Asset<'pod'> {}
+
+export class ContainerAsset extends Asset<'container'> {}
+
+export class ServiceAsset extends Asset<'service'> {}
+
+export type AssetDocument =
+ | AssetKindDocument<'host'>
+ | AssetKindDocument<'pod'>
+ | AssetKindDocument<'container'>
+ | AssetKindDocument<'service'>;
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/container.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/container.ts
new file mode 100644
index 0000000000000..2df8aa2d71ea3
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/container.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+/* eslint-disable max-classes-per-file */
+import { ContainerAsset } from '../assets';
+import { Entity, Fields } from '../entity';
+import { Serializable } from '../serializable';
+
+interface ContainerDocument extends Fields {
+ 'container.id': string;
+ 'kubernetes.pod.uid': string;
+ 'kubernetes.node.name': string;
+}
+
+export class Container extends Entity {
+ metrics() {
+ return new ContainerMetrics({
+ ...this.fields,
+ 'kubernetes.container.cpu.usage.limit.pct': 46,
+ });
+ }
+
+ asset() {
+ return new ContainerAsset({
+ 'asset.kind': 'container',
+ 'asset.id': this.fields['container.id'],
+ 'asset.name': this.fields['container.id'],
+ 'asset.ean': `container:${this.fields['container.id']}`,
+ });
+ }
+}
+
+export interface ContainerMetricsDocument extends ContainerDocument {
+ 'kubernetes.container.cpu.usage.limit.pct': number;
+}
+
+class ContainerMetrics extends Serializable {}
+
+export function container(id: string, uid: string, nodeName: string) {
+ return new Container({
+ 'container.id': id,
+ 'kubernetes.pod.uid': uid,
+ 'kubernetes.node.name': nodeName,
+ });
+}
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts
new file mode 100644
index 0000000000000..8da22d5b140d7
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+/* eslint-disable max-classes-per-file */
+import { HostAsset } from '../assets';
+import { Entity, Fields } from '../entity';
+import { Serializable } from '../serializable';
+import { pod } from './pod';
+
+interface HostDocument extends Fields {
+ 'host.hostname': string;
+}
+
+class Host extends Entity {
+ metrics() {
+ return new HostMetrics({
+ ...this.fields,
+ 'system.cpu.total.norm.pct': 46,
+ });
+ }
+
+ asset() {
+ return new HostAsset({
+ 'asset.kind': 'host',
+ 'asset.id': this.fields['host.hostname'],
+ 'asset.name': this.fields['host.hostname'],
+ 'asset.ean': `host:${this.fields['host.hostname']}`,
+ });
+ }
+
+ pod(uid: string) {
+ return pod(uid, this.fields['host.hostname']);
+ }
+}
+
+export interface HostMetricsDocument extends HostDocument {
+ 'system.cpu.total.norm.pct': number;
+}
+
+class HostMetrics extends Serializable {}
+
+export function host(name: string): Host {
+ return new Host({
+ 'host.hostname': name,
+ });
+}
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts
new file mode 100644
index 0000000000000..961225670e27b
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { container, ContainerMetricsDocument } from './container';
+import { host, HostMetricsDocument } from './host';
+import { pod, PodMetricsDocument } from './pod';
+
+export type InfraDocument = HostMetricsDocument | PodMetricsDocument | ContainerMetricsDocument;
+
+export const infra = {
+ host,
+ pod,
+ container,
+};
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/pod.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/pod.ts
new file mode 100644
index 0000000000000..4876fd7291f53
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/pod.ts
@@ -0,0 +1,53 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+/* eslint-disable max-classes-per-file */
+import { PodAsset } from '../assets';
+import { Entity, Fields } from '../entity';
+import { Serializable } from '../serializable';
+import { container } from './container';
+
+interface PodDocument extends Fields {
+ 'kubernetes.pod.uid': string;
+ 'kubernetes.node.name': string;
+}
+
+export class Pod extends Entity {
+ metrics() {
+ return new PodMetrics({
+ ...this.fields,
+ 'kubernetes.pod.cpu.usage.limit.pct': 46,
+ });
+ }
+
+ asset() {
+ return new PodAsset({
+ 'asset.kind': 'pod',
+ 'asset.id': this.fields['kubernetes.pod.uid'],
+ 'asset.name': this.fields['kubernetes.pod.uid'],
+ 'asset.ean': `pod:${this.fields['kubernetes.pod.uid']}`,
+ });
+ }
+
+ container(id: string) {
+ return container(id, this.fields['kubernetes.pod.uid'], this.fields['kubernetes.node.name']);
+ }
+}
+
+export interface PodMetricsDocument extends PodDocument {
+ 'kubernetes.pod.cpu.usage.limit.pct': number;
+}
+
+class PodMetrics extends Serializable {}
+
+export function pod(uid: string, nodeName: string) {
+ return new Pod({
+ 'kubernetes.pod.uid': uid,
+ 'kubernetes.node.name': nodeName,
+ });
+}
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/monitoring/cluster.ts b/packages/kbn-apm-synthtrace-client/src/lib/monitoring/cluster.ts
new file mode 100644
index 0000000000000..a9e5c66571152
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/monitoring/cluster.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Entity, Fields } from '../entity';
+import { generateShortId } from '../utils/generate_id';
+import { clusterStats } from './cluster_stats';
+import { kibana } from './kibana';
+
+interface ClusterDocument extends Fields {
+ cluster_name: string;
+ cluster_uuid: string;
+}
+
+class Cluster extends Entity {
+ stats() {
+ return clusterStats(this.fields.cluster_name, this.fields.cluster_uuid);
+ }
+
+ kibana(name: string, index: string = '.kibana') {
+ return kibana(name, generateShortId(), this.fields.cluster_uuid, index);
+ }
+}
+
+export function cluster(name: string) {
+ return new Cluster({
+ cluster_name: name,
+ cluster_uuid: generateShortId(),
+ });
+}
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/monitoring/cluster_stats.ts b/packages/kbn-apm-synthtrace-client/src/lib/monitoring/cluster_stats.ts
new file mode 100644
index 0000000000000..7afabe8a30374
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/monitoring/cluster_stats.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Fields } from '../entity';
+import { Serializable } from '../serializable';
+
+export interface ClusterStatsDocument extends Fields {
+ cluster_name: string;
+ cluster_uuid: string;
+ type: 'cluster_stats';
+ 'license.status'?: string;
+ 'cluster_stats.timestamp'?: string;
+ 'cluster_stats.indices.count'?: number;
+}
+
+export class ClusterStats extends Serializable {
+ constructor(fields: ClusterStatsDocument) {
+ super(fields);
+
+ this.fields.type = 'cluster_stats';
+ this.fields['license.status'] = 'active';
+ }
+
+ timestamp(timestamp: number) {
+ super.timestamp(timestamp);
+ this.fields['cluster_stats.timestamp'] = new Date(timestamp).toISOString();
+ return this;
+ }
+
+ indices(count: number) {
+ this.fields['cluster_stats.indices.count'] = count;
+ return this;
+ }
+}
+
+export function clusterStats(name: string, uuid: string) {
+ return new ClusterStats({
+ cluster_name: name,
+ cluster_uuid: uuid,
+ type: 'cluster_stats',
+ });
+}
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/monitoring/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/monitoring/index.ts
new file mode 100644
index 0000000000000..d5ab83eb5773e
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/monitoring/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { cluster } from './cluster';
+import { ClusterStatsDocument } from './cluster_stats';
+import { kibana } from './kibana';
+import { KibanaStatsDocument } from './kibana_stats';
+
+export type MonitoringDocument = ClusterStatsDocument | KibanaStatsDocument;
+
+export const monitoring = {
+ cluster,
+ kibana,
+};
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/monitoring/kibana.ts b/packages/kbn-apm-synthtrace-client/src/lib/monitoring/kibana.ts
new file mode 100644
index 0000000000000..116a283a09e03
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/monitoring/kibana.ts
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Entity, Fields } from '../entity';
+import { kibanaStats } from './kibana_stats';
+
+interface KibanaDocument extends Fields {
+ cluster_uuid: string;
+ 'kibana_stats.kibana.name': string;
+ 'kibana_stats.kibana.uuid': string;
+ 'kibana_stats.kibana.index': string;
+}
+
+export class Kibana extends Entity {
+ stats() {
+ return kibanaStats(
+ this.fields.cluster_uuid,
+ this.fields['kibana_stats.kibana.name'],
+ this.fields['kibana_stats.kibana.uuid'],
+ this.fields['kibana_stats.kibana.index']
+ );
+ }
+}
+
+export function kibana(name: string, uuid: string, clusterUuid: string, index: string = '.kibana') {
+ return new Kibana({
+ cluster_uuid: clusterUuid,
+ 'kibana_stats.kibana.name': name,
+ 'kibana_stats.kibana.uuid': uuid,
+ 'kibana_stats.kibana.index': index,
+ });
+}
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/monitoring/kibana_stats.ts b/packages/kbn-apm-synthtrace-client/src/lib/monitoring/kibana_stats.ts
new file mode 100644
index 0000000000000..e517ba0c63ef6
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/monitoring/kibana_stats.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Fields } from '../entity';
+import { Serializable } from '../serializable';
+
+export interface KibanaStatsDocument extends Fields {
+ type: 'kibana_stats';
+ cluster_uuid: string;
+ 'kibana_stats.kibana.name': string;
+ 'kibana_stats.kibana.uuid': string;
+ 'kibana_stats.kibana.index': string;
+ 'kibana_stats.timestamp'?: string;
+ 'kibana_stats.response_times.max'?: number;
+ 'kibana_stats.kibana.status'?: string;
+ 'kibana_stats.requests.disconnects'?: number;
+ 'kibana_stats.requests.total'?: number;
+}
+
+export class KibanaStats extends Serializable {
+ timestamp(timestamp: number) {
+ super.timestamp(timestamp);
+ this.fields['kibana_stats.timestamp'] = new Date(timestamp).toISOString();
+ return this;
+ }
+
+ status(status: string) {
+ this.fields['kibana_stats.kibana.status'] = status;
+ }
+
+ responseTimes(max: number) {
+ this.fields['kibana_stats.response_times.max'] = max;
+ }
+
+ requests(disconnects: number, total: number) {
+ this.fields['kibana_stats.requests.disconnects'] = disconnects;
+ this.fields['kibana_stats.requests.total'] = total;
+ return this;
+ }
+}
+
+export function kibanaStats(name: string, uuid: string, clusterUuid: string, index: string) {
+ return new KibanaStats({
+ type: 'kibana_stats',
+ cluster_uuid: clusterUuid,
+ 'kibana_stats.kibana.name': name,
+ 'kibana_stats.kibana.uuid': uuid,
+ 'kibana_stats.kibana.index': index,
+ });
+}
diff --git a/packages/kbn-apm-synthtrace/index.ts b/packages/kbn-apm-synthtrace/index.ts
index e5cc06872c3a8..abcf201bf96c2 100644
--- a/packages/kbn-apm-synthtrace/index.ts
+++ b/packages/kbn-apm-synthtrace/index.ts
@@ -10,3 +10,9 @@ export { createLogger, LogLevel } from './src/lib/utils/create_logger';
export { ApmSynthtraceEsClient } from './src/lib/apm/client/apm_synthtrace_es_client';
export { ApmSynthtraceKibanaClient } from './src/lib/apm/client/apm_synthtrace_kibana_client';
+
+export { InfraSynthtraceEsClient } from './src/lib/infra/infra_synthtrace_es_client';
+
+export { AssetsSynthtraceEsClient } from './src/lib/assets/assets_synthtrace_es_client';
+
+export { MonitoringSynthtraceEsClient } from './src/lib/monitoring/monitoring_synthtrace_es_client';
diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/apm_pipeline.ts b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/apm_pipeline.ts
new file mode 100644
index 0000000000000..bd6acc25431a7
--- /dev/null
+++ b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/apm_pipeline.ts
@@ -0,0 +1,59 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { PassThrough, pipeline, Readable } from 'stream';
+import { getDedotTransform } from '../../../shared/get_dedot_transform';
+import { getSerializeTransform } from '../../../shared/get_serialize_transform';
+import { Logger } from '../../../utils/create_logger';
+import { fork } from '../../../utils/stream_utils';
+import { createBreakdownMetricsAggregator } from '../../aggregators/create_breakdown_metrics_aggregator';
+import { createServiceMetricsAggregator } from '../../aggregators/create_service_metrics_aggregator';
+import { createServiceSummaryMetricsAggregator } from '../../aggregators/create_service_summary_metrics_aggregator';
+import { createSpanMetricsAggregator } from '../../aggregators/create_span_metrics_aggregator';
+import { createTransactionMetricsAggregator } from '../../aggregators/create_transaction_metrics_aggregator';
+import { getApmServerMetadataTransform } from './get_apm_server_metadata_transform';
+import { getIntakeDefaultsTransform } from './get_intake_defaults_transform';
+import { getRoutingTransform } from './get_routing_transform';
+
+export function apmPipeline(logger: Logger, version: string, includeSerialization: boolean = true) {
+ return (base: Readable) => {
+ const aggregators = [
+ createTransactionMetricsAggregator('1m'),
+ createTransactionMetricsAggregator('10m'),
+ createTransactionMetricsAggregator('60m'),
+ createServiceMetricsAggregator('1m'),
+ createServiceMetricsAggregator('10m'),
+ createServiceMetricsAggregator('60m'),
+ createServiceSummaryMetricsAggregator('1m'),
+ createServiceSummaryMetricsAggregator('10m'),
+ createServiceSummaryMetricsAggregator('60m'),
+ createSpanMetricsAggregator('1m'),
+ createSpanMetricsAggregator('10m'),
+ createSpanMetricsAggregator('60m'),
+ ];
+
+ const serializationTransform = includeSerialization ? [getSerializeTransform()] : [];
+
+ return pipeline(
+ // @ts-expect-error Some weird stuff here with the type definition for pipeline. We have tests!
+ base,
+ ...serializationTransform,
+ getIntakeDefaultsTransform(),
+ fork(new PassThrough({ objectMode: true }), ...aggregators),
+ createBreakdownMetricsAggregator('30s'),
+ getApmServerMetadataTransform(version),
+ getRoutingTransform(),
+ getDedotTransform(),
+ (err) => {
+ if (err) {
+ logger.error(err);
+ }
+ }
+ );
+ };
+}
diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/index.ts b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/index.ts
index 21fd3a0ac7dcf..310c33c10483d 100644
--- a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/index.ts
+++ b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/index.ts
@@ -7,38 +7,11 @@
*/
import { Client, estypes } from '@elastic/elasticsearch';
-import {
- ApmFields,
- ESDocumentWithOperation,
- SynthtraceESAction,
- SynthtraceGenerator,
-} from '@kbn/apm-synthtrace-client';
-import { castArray } from 'lodash';
-import { PassThrough, pipeline, Readable, Transform } from 'stream';
-import { isGeneratorObject } from 'util/types';
+import { ApmFields } from '@kbn/apm-synthtrace-client';
import { ValuesType } from 'utility-types';
+import { SynthtraceEsClient, SynthtraceEsClientOptions } from '../../../shared/base_client';
import { Logger } from '../../../utils/create_logger';
-import { fork, sequential } from '../../../utils/stream_utils';
-import { createBreakdownMetricsAggregator } from '../../aggregators/create_breakdown_metrics_aggregator';
-import { createServiceMetricsAggregator } from '../../aggregators/create_service_metrics_aggregator';
-import { createServiceSummaryMetricsAggregator } from '../../aggregators/create_service_summary_metrics_aggregator';
-import { createSpanMetricsAggregator } from '../../aggregators/create_span_metrics_aggregator';
-import { createTransactionMetricsAggregator } from '../../aggregators/create_transaction_metrics_aggregator';
-import { getApmServerMetadataTransform } from './get_apm_server_metadata_transform';
-import { getDedotTransform } from './get_dedot_transform';
-import { getIntakeDefaultsTransform } from './get_intake_defaults_transform';
-import { getRoutingTransform } from './get_routing_transform';
-import { getSerializeTransform } from './get_serialize_transform';
-
-export interface ApmSynthtraceEsClientOptions {
- version: string;
- concurrency?: number;
- refreshAfterIndex?: boolean;
-}
-
-type MaybeArray = T | T[];
-
-const DATA_STREAMS = ['traces-apm*', 'metrics-apm*', 'logs-apm*'];
+import { apmPipeline } from './apm_pipeline';
export enum ComponentTemplateName {
LogsApp = 'logs-apm.app@custom',
@@ -50,33 +23,20 @@ export enum ComponentTemplateName {
TracesApmSampled = 'traces-apm.sampled@custom',
}
-export class ApmSynthtraceEsClient {
- private readonly client: Client;
- private readonly logger: Logger;
-
- private readonly concurrency: number;
-
- private readonly refreshAfterIndex: boolean;
-
- private readonly version: string;
+export interface ApmSynthtraceEsClientOptions extends Omit {
+ version: string;
+}
- private pipelineCallback: (base: Readable) => NodeJS.WritableStream = this.getDefaultPipeline();
+export class ApmSynthtraceEsClient extends SynthtraceEsClient {
+ private version: string;
constructor(options: { client: Client; logger: Logger } & ApmSynthtraceEsClientOptions) {
- this.client = options.client;
- this.logger = options.logger;
- this.concurrency = options.concurrency ?? 1;
- this.refreshAfterIndex = options.refreshAfterIndex ?? false;
- this.version = options.version;
- }
-
- async clean() {
- this.logger.info(`Cleaning APM data streams ${DATA_STREAMS.join(',')}`);
-
- await this.client.indices.deleteDataStream({
- name: DATA_STREAMS.join(','),
- expand_wildcards: ['open', 'hidden'],
+ super({
+ ...options,
+ pipeline: apmPipeline(options.logger, options.version),
});
+ this.dataStreams = ['traces-apm*', 'metrics-apm*', 'logs-apm*'];
+ this.version = options.version;
}
async updateComponentTemplate(
@@ -105,117 +65,7 @@ export class ApmSynthtraceEsClient {
this.logger.info(`Updated component template: ${name}`);
}
- async refresh(dataStreams: string[] = DATA_STREAMS) {
- this.logger.info(`Refreshing ${dataStreams.join(',')}`);
-
- return this.client.indices.refresh({
- index: dataStreams,
- allow_no_indices: true,
- ignore_unavailable: true,
- expand_wildcards: ['open', 'hidden'],
- });
- }
-
getDefaultPipeline(includeSerialization: boolean = true) {
- return (base: Readable) => {
- const aggregators = [
- createTransactionMetricsAggregator('1m'),
- createTransactionMetricsAggregator('10m'),
- createTransactionMetricsAggregator('60m'),
- createServiceMetricsAggregator('1m'),
- createServiceMetricsAggregator('10m'),
- createServiceMetricsAggregator('60m'),
- createServiceSummaryMetricsAggregator('1m'),
- createServiceSummaryMetricsAggregator('10m'),
- createServiceSummaryMetricsAggregator('60m'),
- createSpanMetricsAggregator('1m'),
- createSpanMetricsAggregator('10m'),
- createSpanMetricsAggregator('60m'),
- ];
-
- const serializationTransform = includeSerialization ? [getSerializeTransform()] : [];
-
- return pipeline(
- // @ts-expect-error Some weird stuff here with the type definition for pipeline. We have tests!
- base,
- ...serializationTransform,
- getIntakeDefaultsTransform(),
- fork(new PassThrough({ objectMode: true }), ...aggregators),
- createBreakdownMetricsAggregator('30s'),
- getApmServerMetadataTransform(this.version),
- getRoutingTransform(),
- getDedotTransform(),
- (err) => {
- if (err) {
- this.logger.error(err);
- }
- }
- );
- };
- }
-
- pipeline(cb: (base: Readable) => NodeJS.WritableStream) {
- this.pipelineCallback = cb;
- }
-
- getVersion() {
- return this.version;
- }
-
- async index(streamOrGenerator: MaybeArray>) {
- this.logger.debug(`Bulk indexing ${castArray(streamOrGenerator).length} stream(s)`);
-
- const allStreams = castArray(streamOrGenerator).map((obj) => {
- const base = isGeneratorObject(obj) ? Readable.from(obj) : obj;
-
- return this.pipelineCallback(base);
- }) as Transform[];
-
- let count: number = 0;
-
- const stream = sequential(...allStreams);
-
- await this.client.helpers.bulk({
- concurrency: this.concurrency,
- refresh: false,
- refreshOnCompletion: false,
- flushBytes: 250000,
- datasource: stream,
- filter_path: 'errors,items.*.error,items.*.status',
- onDocument: (doc: ESDocumentWithOperation) => {
- let action: SynthtraceESAction;
- count++;
-
- if (count % 100000 === 0) {
- this.logger.info(`Indexed ${count} documents`);
- } else if (count % 1000 === 0) {
- this.logger.debug(`Indexed ${count} documents`);
- }
-
- if (doc._action) {
- action = doc._action!;
- delete doc._action;
- } else if (doc._index) {
- action = { create: { _index: doc._index } };
- delete doc._index;
- } else {
- this.logger.debug(doc);
- throw new Error(
- `Could not determine operation: _index and _action not defined in document`
- );
- }
-
- return action;
- },
- onDrop: (doc) => {
- this.logger.error(`Dropped document: ${JSON.stringify(doc, null, 2)}`);
- },
- });
-
- this.logger.info(`Produced ${count} events`);
-
- if (this.refreshAfterIndex) {
- await this.refresh();
- }
+ return apmPipeline(this.logger, this.version, includeSerialization);
}
}
diff --git a/packages/kbn-apm-synthtrace/src/lib/assets/assets_synthtrace_es_client.ts b/packages/kbn-apm-synthtrace/src/lib/assets/assets_synthtrace_es_client.ts
new file mode 100644
index 0000000000000..3f74304119d78
--- /dev/null
+++ b/packages/kbn-apm-synthtrace/src/lib/assets/assets_synthtrace_es_client.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Client } from '@elastic/elasticsearch';
+import { AssetDocument, ESDocumentWithOperation } from '@kbn/apm-synthtrace-client';
+import { pipeline, Readable, Transform } from 'stream';
+import { SynthtraceEsClient, SynthtraceEsClientOptions } from '../shared/base_client';
+import { getDedotTransform } from '../shared/get_dedot_transform';
+import { getSerializeTransform } from '../shared/get_serialize_transform';
+import { Logger } from '../utils/create_logger';
+
+export type AssetsSynthtraceEsClientOptions = Omit;
+
+export class AssetsSynthtraceEsClient extends SynthtraceEsClient {
+ constructor(options: { client: Client; logger: Logger } & AssetsSynthtraceEsClientOptions) {
+ super({
+ ...options,
+ pipeline: assetsPipeline(),
+ });
+ this.dataStreams = ['assets-*'];
+ }
+}
+
+function assetsPipeline() {
+ return (base: Readable) => {
+ return pipeline(
+ base,
+ getSerializeTransform(),
+ getRoutingTransform(),
+ getDedotTransform(),
+ (err: unknown) => {
+ if (err) {
+ throw err;
+ }
+ }
+ );
+ };
+}
+
+function getRoutingTransform() {
+ return new Transform({
+ objectMode: true,
+ transform(document: ESDocumentWithOperation, encoding, callback) {
+ if ('asset.kind' in document) {
+ document._index = `assets-${document['asset.kind']}-default`;
+ } else {
+ throw new Error('Cannot determine index for event');
+ }
+
+ callback(null, document);
+ },
+ });
+}
diff --git a/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_es_client.ts b/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_es_client.ts
new file mode 100644
index 0000000000000..897d813ae6718
--- /dev/null
+++ b/packages/kbn-apm-synthtrace/src/lib/infra/infra_synthtrace_es_client.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Client } from '@elastic/elasticsearch';
+import { ESDocumentWithOperation, InfraDocument } from '@kbn/apm-synthtrace-client';
+import { pipeline, Readable, Transform } from 'stream';
+import { SynthtraceEsClient, SynthtraceEsClientOptions } from '../shared/base_client';
+import { getDedotTransform } from '../shared/get_dedot_transform';
+import { getSerializeTransform } from '../shared/get_serialize_transform';
+import { Logger } from '../utils/create_logger';
+
+export type InfraSynthtraceEsClientOptions = Omit;
+
+export class InfraSynthtraceEsClient extends SynthtraceEsClient {
+ constructor(options: { client: Client; logger: Logger } & InfraSynthtraceEsClientOptions) {
+ super({
+ ...options,
+ pipeline: infraPipeline(),
+ });
+ this.dataStreams = ['metrics-*', 'logs-*'];
+ }
+}
+
+function infraPipeline() {
+ return (base: Readable) => {
+ return pipeline(
+ base,
+ getSerializeTransform(),
+ getRoutingTransform(),
+ getDedotTransform(),
+ (err: unknown) => {
+ if (err) {
+ throw err;
+ }
+ }
+ );
+ };
+}
+
+function getRoutingTransform() {
+ return new Transform({
+ objectMode: true,
+ transform(document: ESDocumentWithOperation, encoding, callback) {
+ if ('host.hostname' in document) {
+ document._index = 'metrics-system.cpu-default';
+ } else if ('container.id' in document) {
+ document._index = 'metrics-kubernetes.container-default';
+ } else if ('kubernetes.pod.uid' in document) {
+ document._index = 'metrics-kubernetes.pod-default';
+ } else {
+ throw new Error('Cannot determine index for event');
+ }
+
+ callback(null, document);
+ },
+ });
+}
diff --git a/packages/kbn-apm-synthtrace/src/lib/monitoring/monitoring_synthtrace_es_client.ts b/packages/kbn-apm-synthtrace/src/lib/monitoring/monitoring_synthtrace_es_client.ts
new file mode 100644
index 0000000000000..6ef6d770ce6d4
--- /dev/null
+++ b/packages/kbn-apm-synthtrace/src/lib/monitoring/monitoring_synthtrace_es_client.ts
@@ -0,0 +1,64 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Client } from '@elastic/elasticsearch';
+import { ESDocumentWithOperation, MonitoringDocument } from '@kbn/apm-synthtrace-client';
+import { pipeline, Readable, Transform } from 'stream';
+import { SynthtraceEsClient, SynthtraceEsClientOptions } from '../shared/base_client';
+import { getDedotTransform } from '../shared/get_dedot_transform';
+import { getSerializeTransform } from '../shared/get_serialize_transform';
+import { Logger } from '../utils/create_logger';
+
+export type MonitoringSynthtraceEsClientOptions = Omit;
+
+export class MonitoringSynthtraceEsClient extends SynthtraceEsClient {
+ constructor(options: { client: Client; logger: Logger } & MonitoringSynthtraceEsClientOptions) {
+ super({
+ ...options,
+ pipeline: monitoringPipeline(),
+ });
+ this.dataStreams = ['.monitoring-*', 'metrics-*'];
+ }
+}
+
+function monitoringPipeline() {
+ return (base: Readable) => {
+ return pipeline(
+ base,
+ getSerializeTransform(),
+ getRoutingTransform(),
+ getDedotTransform(),
+ (err: unknown) => {
+ if (err) {
+ throw err;
+ }
+ }
+ );
+ };
+}
+
+function getRoutingTransform() {
+ return new Transform({
+ objectMode: true,
+ transform(document: ESDocumentWithOperation, encoding, callback) {
+ if ('type' in document) {
+ if (document.type === 'cluster_stats') {
+ document._index = '.monitoring-es-7';
+ } else if (document.type === 'kibana_stats') {
+ document._index = '.monitoring-kibana-7';
+ } else {
+ throw new Error('Cannot determine index for event');
+ }
+ } else {
+ throw new Error('Cannot determine index for event');
+ }
+
+ callback(null, document);
+ },
+ });
+}
diff --git a/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts b/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts
new file mode 100644
index 0000000000000..4a3a79e2b78d9
--- /dev/null
+++ b/packages/kbn-apm-synthtrace/src/lib/shared/base_client.ts
@@ -0,0 +1,128 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { Client } from '@elastic/elasticsearch';
+import {
+ ESDocumentWithOperation,
+ Fields,
+ SynthtraceESAction,
+ SynthtraceGenerator,
+} from '@kbn/apm-synthtrace-client';
+import { castArray } from 'lodash';
+import { Readable, Transform } from 'stream';
+import { isGeneratorObject } from 'util/types';
+import { Logger } from '../utils/create_logger';
+import { sequential } from '../utils/stream_utils';
+
+export interface SynthtraceEsClientOptions {
+ concurrency?: number;
+ refreshAfterIndex?: boolean;
+ pipeline: (base: Readable) => NodeJS.WritableStream;
+}
+
+type MaybeArray = T | T[];
+
+export class SynthtraceEsClient {
+ protected readonly client: Client;
+ protected readonly logger: Logger;
+
+ private readonly concurrency: number;
+ private readonly refreshAfterIndex: boolean;
+
+ private pipelineCallback: (base: Readable) => NodeJS.WritableStream;
+ protected dataStreams: string[] = [];
+
+ constructor(options: { client: Client; logger: Logger } & SynthtraceEsClientOptions) {
+ this.client = options.client;
+ this.logger = options.logger;
+ this.concurrency = options.concurrency ?? 1;
+ this.refreshAfterIndex = options.refreshAfterIndex ?? false;
+ this.pipelineCallback = options.pipeline;
+ }
+
+ async clean() {
+ this.logger.info(`Cleaning data streams ${this.dataStreams.join(',')}`);
+
+ await this.client.indices.deleteDataStream({
+ name: this.dataStreams.join(','),
+ expand_wildcards: ['open', 'hidden'],
+ });
+ }
+
+ async refresh() {
+ this.logger.info(`Refreshing ${this.dataStreams.join(',')}`);
+
+ return this.client.indices.refresh({
+ index: this.dataStreams,
+ allow_no_indices: true,
+ ignore_unavailable: true,
+ expand_wildcards: ['open', 'hidden'],
+ });
+ }
+
+ pipeline(cb: (base: Readable) => NodeJS.WritableStream) {
+ this.pipelineCallback = cb;
+ }
+
+ async index(streamOrGenerator: MaybeArray>) {
+ this.logger.debug(`Bulk indexing ${castArray(streamOrGenerator).length} stream(s)`);
+
+ const allStreams = castArray(streamOrGenerator).map((obj) => {
+ const base = isGeneratorObject(obj) ? Readable.from(obj) : obj;
+
+ return this.pipelineCallback(base);
+ }) as Transform[];
+
+ let count: number = 0;
+
+ const stream = sequential(...allStreams);
+
+ await this.client.helpers.bulk({
+ concurrency: this.concurrency,
+ refresh: false,
+ refreshOnCompletion: false,
+ flushBytes: 250000,
+ datasource: stream,
+ filter_path: 'errors,items.*.error,items.*.status',
+ onDocument: (doc: ESDocumentWithOperation) => {
+ let action: SynthtraceESAction;
+ count++;
+
+ if (count % 100000 === 0) {
+ this.logger.info(`Indexed ${count} documents`);
+ } else if (count % 1000 === 0) {
+ this.logger.debug(`Indexed ${count} documents`);
+ }
+
+ if (doc._action) {
+ action = doc._action!;
+ delete doc._action;
+ } else if (doc._index) {
+ action = { create: { _index: doc._index } };
+ delete doc._index;
+ } else {
+ this.logger.debug(doc);
+ throw new Error(
+ `Could not determine operation: _index and _action not defined in document`
+ );
+ }
+
+ return action;
+ },
+ onDrop: (doc) => {
+ this.logger.error(`Dropped document: ${JSON.stringify(doc, null, 2)}`);
+ },
+ });
+
+ this.logger.info(`Produced ${count} events`);
+
+ if (this.refreshAfterIndex) {
+ await this.refresh();
+ }
+ }
+}
diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/get_dedot_transform.ts b/packages/kbn-apm-synthtrace/src/lib/shared/get_dedot_transform.ts
similarity index 100%
rename from packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/get_dedot_transform.ts
rename to packages/kbn-apm-synthtrace/src/lib/shared/get_dedot_transform.ts
diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/get_serialize_transform.ts b/packages/kbn-apm-synthtrace/src/lib/shared/get_serialize_transform.ts
similarity index 100%
rename from packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_es_client/get_serialize_transform.ts
rename to packages/kbn-apm-synthtrace/src/lib/shared/get_serialize_transform.ts
diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts
index 9fd448d1099a2..959308de9d5fe 100644
--- a/packages/kbn-doc-links/src/get_doc_links.ts
+++ b/packages/kbn-doc-links/src/get_doc_links.ts
@@ -484,7 +484,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => {
classificationAucRoc: `${MACHINE_LEARNING_DOCS}ml-dfa-classification.html#ml-dfanalytics-class-aucroc`,
setUpgradeMode: `${ELASTICSEARCH_DOCS}ml-set-upgrade-mode.html`,
trainedModels: `${MACHINE_LEARNING_DOCS}ml-trained-models.html`,
- startTrainedModelsDeployment: `${MACHINE_LEARNING_DOCS}ml-nlp-deploy-models.html#ml-nlp-deploy-model`,
+ startTrainedModelsDeployment: `${MACHINE_LEARNING_DOCS}ml-nlp-deploy-model.html`,
},
transforms: {
guide: `${ELASTICSEARCH_DOCS}transforms.html`,
diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts
index 3940146cbfe85..6ac0ce93bec2b 100644
--- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts
+++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts
@@ -141,7 +141,12 @@ describe('esArchiver: createCreateIndexStream()', () => {
body: {
settings: undefined,
mappings: undefined,
- aliases: { foo: {} },
+ },
+ });
+
+ sinon.assert.calledWith(client.indices.updateAliases as sinon.SinonSpy, {
+ body: {
+ actions: [{ add: { alias: 'foo', index: 'index' } }],
},
});
});
@@ -299,6 +304,7 @@ describe('esArchiver: createCreateIndexStream()', () => {
]);
sinon.assert.notCalled(client.indices.create as sinon.SinonSpy);
+ sinon.assert.notCalled(client.indices.updateAliases as sinon.SinonSpy);
expect(output).toEqual([createStubDocRecord('index', 1)]);
});
});
@@ -310,8 +316,8 @@ describe('esArchiver: createCreateIndexStream()', () => {
await createPromiseFromStreams([
createListStream([
- createStubIndexRecord('new-index'),
- createStubIndexRecord('existing-index'),
+ createStubIndexRecord('new-index', { 'new-index-alias': {} }),
+ createStubIndexRecord('existing-index', { 'existing-index-alias': {} }),
]),
createCreateIndexStream({
client,
@@ -331,6 +337,12 @@ describe('esArchiver: createCreateIndexStream()', () => {
'index',
'new-index'
);
+
+ // only update aliases for the 'new-index'
+ sinon.assert.callCount(client.indices.updateAliases as sinon.SinonSpy, 1);
+ expect((client.indices.updateAliases as sinon.SinonSpy).args[0][0]).toHaveProperty('body', {
+ actions: [{ add: { alias: 'new-index-alias', index: 'new-index' } }],
+ });
});
it('filters documents for skipped indices', async () => {
diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts
index 154c471a07def..3908ff375d0dd 100644
--- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts
+++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts
@@ -141,13 +141,13 @@ export function createCreateIndexStream({
log.debug(`Deleted saved object index [${index}]`);
}
+ // create the index without the aliases
await client.indices.create(
{
index,
body: {
settings,
mappings,
- aliases,
},
},
{
@@ -155,6 +155,21 @@ export function createCreateIndexStream({
}
);
+ // create the aliases on a separate step (see https://github.com/elastic/kibana/issues/158918)
+ const actions: estypes.IndicesUpdateAliasesAction[] = Object.keys(aliases ?? {}).map(
+ (alias) => ({
+ add: {
+ index,
+ alias,
+ ...aliases![alias],
+ },
+ })
+ );
+
+ if (actions.length) {
+ await client.indices.updateAliases({ body: { actions } });
+ }
+
stats.createdIndex(index, { settings });
} catch (err) {
if (
diff --git a/src/plugins/data/common/search/aggs/metrics/filtered_metric.test.ts b/src/plugins/data/common/search/aggs/metrics/filtered_metric.test.ts
index b752b6e24fbcb..f951ecc065a9c 100644
--- a/src/plugins/data/common/search/aggs/metrics/filtered_metric.test.ts
+++ b/src/plugins/data/common/search/aggs/metrics/filtered_metric.test.ts
@@ -7,25 +7,36 @@
*/
import { AggConfigs, IAggConfigs } from '../agg_configs';
-import { mockAggTypesRegistry } from '../test_helpers';
+import { AggTypesDependencies } from '../agg_types';
+import { mockAggTypesDependencies, mockAggTypesRegistry } from '../test_helpers';
+import { getFilteredMetricAgg } from './filtered_metric';
+import { IMetricAggConfig } from './metric_agg_type';
import { METRIC_TYPES } from './metric_agg_types';
describe('filtered metric agg type', () => {
let aggConfigs: IAggConfigs;
+ let aggTypesDependencies: AggTypesDependencies;
+ const typesRegistry = mockAggTypesRegistry();
+ const field = {
+ name: 'bytes',
+ filterable: true,
+ };
+ const indexPattern = {
+ id: '1234',
+ title: 'logstash-*',
+ fields: {
+ getByName: () => field,
+ filter: () => [field],
+ find: () => field,
+ },
+ } as any;
beforeEach(() => {
- const typesRegistry = mockAggTypesRegistry();
- const field = {
- name: 'bytes',
+ jest.resetAllMocks();
+ aggTypesDependencies = {
+ ...mockAggTypesDependencies,
+ getConfig: jest.fn(),
};
- const indexPattern = {
- id: '1234',
- title: 'logstash-*',
- fields: {
- getByName: () => field,
- filter: () => [field],
- },
- } as any;
aggConfigs = new AggConfigs(
indexPattern,
@@ -76,4 +87,53 @@ describe('filtered metric agg type', () => {
expect(agg.getResponseId()).toEqual('filtered_metric-bucket');
});
+ it.each`
+ aggType
+ ${'top_metrics'}
+ ${'top_hits'}
+ `('returns phrase filter for filtered metric for $aggType', ({ aggType }) => {
+ const topMetricsAggConfigs = new AggConfigs(
+ indexPattern,
+ [
+ {
+ id: METRIC_TYPES.FILTERED_METRIC,
+ type: METRIC_TYPES.FILTERED_METRIC,
+ schema: 'metric',
+ params: {
+ customBucket: {
+ type: 'filter',
+ params: {
+ filter: { language: 'kuery', query: 'a: b' },
+ },
+ },
+ customMetric: {
+ type: aggType,
+ params: {
+ field: 'bytes',
+ },
+ },
+ },
+ },
+ ],
+ {
+ typesRegistry,
+ },
+ jest.fn()
+ );
+
+ expect(
+ getFilteredMetricAgg(aggTypesDependencies).createFilter!(
+ topMetricsAggConfigs.aggs[0] as IMetricAggConfig,
+ 10
+ ).query.match_phrase
+ ).toEqual({ bytes: 10 });
+ });
+ it('returns filter from the custom bucket filter parameter for metric', () => {
+ expect(
+ getFilteredMetricAgg(aggTypesDependencies).createFilter!(
+ aggConfigs.aggs[0] as IMetricAggConfig,
+ '10'
+ ).query.bool.filter[0].bool.should[0].match
+ ).toEqual({ bytes: 'b' });
+ });
});
diff --git a/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts b/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts
index abf882dd0466a..3cc47f298ae8f 100644
--- a/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts
+++ b/src/plugins/data/common/search/aggs/metrics/filtered_metric.ts
@@ -51,16 +51,18 @@ export const getFilteredMetricAgg = ({ getConfig }: FiltersMetricAggDependencies
hasNoDslParams: true,
getSerializedFormat,
createFilter: (agg, inputState) => {
+ const indexPattern = agg.getIndexPattern();
+ if (
+ agg.params.customMetric.type.name === 'top_hits' ||
+ agg.params.customMetric.type.name === 'top_metrics'
+ ) {
+ return agg.params.customMetric.createFilter(inputState);
+ }
if (!agg.params.customBucket.params.filter) return;
const esQueryConfigs = getEsQueryConfig({ get: getConfig });
return buildQueryFilter(
- buildEsQuery(
- agg.getIndexPattern(),
- [agg.params.customBucket.params.filter],
- [],
- esQueryConfigs
- ),
- agg.getIndexPattern().id!,
+ buildEsQuery(indexPattern, [agg.params.customBucket.params.filter], [], esQueryConfigs),
+ indexPattern.id!,
agg.params.customBucket.params.filter.query
);
},
diff --git a/src/plugins/data/common/search/aggs/metrics/lib/create_filter.ts b/src/plugins/data/common/search/aggs/metrics/lib/create_filter.ts
index a859e189ccfe5..70730641c97d2 100644
--- a/src/plugins/data/common/search/aggs/metrics/lib/create_filter.ts
+++ b/src/plugins/data/common/search/aggs/metrics/lib/create_filter.ts
@@ -6,7 +6,12 @@
* Side Public License, v 1.
*/
-import { buildExistsFilter } from '@kbn/es-query';
+import {
+ BooleanRelation,
+ buildCombinedFilter,
+ buildExistsFilter,
+ buildPhraseFilter,
+} from '@kbn/es-query';
import { AggConfig } from '../../agg_config';
import { IMetricAggConfig } from '../metric_agg_type';
@@ -19,3 +24,21 @@ export const createMetricFilter = (
+ aggConfig: TMetricAggConfig,
+ key: string
+) => {
+ const indexPattern = aggConfig.getIndexPattern();
+ const field = aggConfig.getField();
+ if (!field) {
+ return;
+ }
+ return Array.isArray(key)
+ ? buildCombinedFilter(
+ BooleanRelation.OR,
+ key.map((k) => buildPhraseFilter(field, k, indexPattern)),
+ indexPattern
+ )
+ : buildPhraseFilter(field, key, indexPattern);
+};
diff --git a/src/plugins/data/common/search/aggs/metrics/top_hit.test.ts b/src/plugins/data/common/search/aggs/metrics/top_hit.test.ts
index 367ac68707095..494d3921538dc 100644
--- a/src/plugins/data/common/search/aggs/metrics/top_hit.test.ts
+++ b/src/plugins/data/common/search/aggs/metrics/top_hit.test.ts
@@ -402,5 +402,10 @@ describe('Top hit metric', () => {
});
});
});
+ it('returns phrase filter', () => {
+ expect(getTopHitMetricAgg().createFilter!(aggConfig, '10').query.match_phrase).toEqual({
+ bytes: 10,
+ });
+ });
});
});
diff --git a/src/plugins/data/common/search/aggs/metrics/top_hit.ts b/src/plugins/data/common/search/aggs/metrics/top_hit.ts
index 367e16c15a2f1..c7826e5966e6f 100644
--- a/src/plugins/data/common/search/aggs/metrics/top_hit.ts
+++ b/src/plugins/data/common/search/aggs/metrics/top_hit.ts
@@ -14,6 +14,7 @@ import { IMetricAggConfig, MetricAggType } from './metric_agg_type';
import { METRIC_TYPES } from './metric_agg_types';
import { flattenHit, KBN_FIELD_TYPES } from '../../..';
import { BaseAggParams } from '../types';
+import { createTopHitFilter } from './lib/create_filter';
export interface BaseAggParamsTopHit extends BaseAggParams {
field: string;
@@ -256,5 +257,6 @@ export const getTopHitMetricAgg = () => {
}
return values;
},
+ createFilter: createTopHitFilter,
});
};
diff --git a/src/plugins/data/common/search/aggs/metrics/top_metrics.test.ts b/src/plugins/data/common/search/aggs/metrics/top_metrics.test.ts
index 7b34a6f4a704f..1c4a1938ee725 100644
--- a/src/plugins/data/common/search/aggs/metrics/top_metrics.test.ts
+++ b/src/plugins/data/common/search/aggs/metrics/top_metrics.test.ts
@@ -11,6 +11,7 @@ import { AggConfigs } from '../agg_configs';
import { mockAggTypesRegistry } from '../test_helpers';
import { IMetricAggConfig } from './metric_agg_type';
import { KBN_FIELD_TYPES } from '../../..';
+import { CombinedFilter } from '@kbn/es-query';
describe('Top metrics metric', () => {
let aggConfig: IMetricAggConfig;
@@ -191,5 +192,15 @@ describe('Top metrics metric', () => {
init({ fieldName: 'bytes' });
expect(getTopMetricsMetricAgg().getValue(aggConfig, bucket)).toEqual([1024, 512, 256]);
});
+ it('returns phrase filter', () => {
+ expect(getTopMetricsMetricAgg().createFilter!(aggConfig, '10').query.match_phrase).toEqual({
+ bytes: 10,
+ });
+ });
+ it('returns combined OR filter for array values', () => {
+ const params = getTopMetricsMetricAgg().createFilter!(aggConfig, ['10', '20']).meta
+ .params as CombinedFilter['meta']['params'];
+ expect(params.map((p) => p.query!.match_phrase)).toEqual([{ bytes: 10 }, { bytes: 20 }]);
+ });
});
});
diff --git a/src/plugins/data/common/search/aggs/metrics/top_metrics.ts b/src/plugins/data/common/search/aggs/metrics/top_metrics.ts
index 354e0c6207d50..35bb6efb77ca3 100644
--- a/src/plugins/data/common/search/aggs/metrics/top_metrics.ts
+++ b/src/plugins/data/common/search/aggs/metrics/top_metrics.ts
@@ -14,6 +14,7 @@ import { IMetricAggConfig, MetricAggType } from './metric_agg_type';
import { METRIC_TYPES } from './metric_agg_types';
import { DataViewField, KBN_FIELD_TYPES } from '../../..';
import { BaseAggParams } from '../types';
+import { createTopHitFilter } from './lib/create_filter';
export interface BaseAggParamsTopMetrics extends BaseAggParams {
field: string;
@@ -162,5 +163,6 @@ export const getTopMetricsMetricAgg = () => {
if (results.length === 1) return results[0];
return results;
},
+ createFilter: createTopHitFilter,
});
};
diff --git a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts
index 9913ac716100e..75cf23e3320db 100644
--- a/src/plugins/discover/public/application/main/services/discover_app_state_container.ts
+++ b/src/plugins/discover/public/application/main/services/discover_app_state_container.ts
@@ -259,8 +259,6 @@ export interface AppStateUrl extends Omit {
sort?: string[][] | [string, string];
}
-export const GLOBAL_STATE_URL_KEY = '_g';
-
export function getInitialState(
stateStorage: IKbnUrlStateStorage | undefined,
savedSearch: SavedSearch,
diff --git a/src/plugins/discover/public/application/main/services/discover_global_state_container.ts b/src/plugins/discover/public/application/main/services/discover_global_state_container.ts
new file mode 100644
index 0000000000000..39e099653039f
--- /dev/null
+++ b/src/plugins/discover/public/application/main/services/discover_global_state_container.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { QueryState } from '@kbn/data-plugin/common';
+import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
+
+export interface DiscoverGlobalStateContainer {
+ get: () => QueryState | null;
+ set: (state: QueryState) => Promise;
+}
+
+const GLOBAL_STATE_URL_KEY = '_g';
+
+export const getDiscoverGlobalStateContainer = (
+ stateStorage: IKbnUrlStateStorage
+): DiscoverGlobalStateContainer => ({
+ get: () => stateStorage.get(GLOBAL_STATE_URL_KEY),
+ set: async (state: QueryState) => {
+ await stateStorage.set(GLOBAL_STATE_URL_KEY, state, { replace: true });
+ },
+});
diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts
index bf74e58346ebf..fe575f9c6dfc8 100644
--- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts
+++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.test.ts
@@ -12,19 +12,22 @@ import { discoverServiceMock } from '../../../__mocks__/services';
import { savedSearchMock, savedSearchMockWithTimeField } from '../../../__mocks__/saved_search';
import { dataViewMock } from '../../../__mocks__/data_view';
import { dataViewComplexMock } from '../../../__mocks__/data_view_complex';
+import { getDiscoverGlobalStateContainer } from './discover_global_state_container';
+import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
describe('DiscoverSavedSearchContainer', () => {
const savedSearch = savedSearchMock;
const services = discoverServiceMock;
+ const globalStateContainer = getDiscoverGlobalStateContainer(createKbnUrlStateStorage());
describe('getTitle', () => {
it('returns undefined for new saved searches', () => {
- const container = getSavedSearchContainer({ services });
+ const container = getSavedSearchContainer({ services, globalStateContainer });
expect(container.getTitle()).toBe(undefined);
});
it('returns the title of a persisted saved searches', () => {
- const container = getSavedSearchContainer({ services });
+ const container = getSavedSearchContainer({ services, globalStateContainer });
container.set(savedSearch);
expect(container.getTitle()).toBe(savedSearch.title);
});
@@ -32,7 +35,7 @@ describe('DiscoverSavedSearchContainer', () => {
describe('set', () => {
it('should update the current and initial state of the saved search', () => {
- const container = getSavedSearchContainer({ services });
+ const container = getSavedSearchContainer({ services, globalStateContainer });
const newSavedSearch: SavedSearch = { ...savedSearch, title: 'New title' };
const result = container.set(newSavedSearch);
@@ -45,7 +48,7 @@ describe('DiscoverSavedSearchContainer', () => {
});
it('should reset hasChanged$ to false', () => {
- const container = getSavedSearchContainer({ services });
+ const container = getSavedSearchContainer({ services, globalStateContainer });
const newSavedSearch: SavedSearch = { ...savedSearch, title: 'New title' };
container.set(newSavedSearch);
@@ -55,7 +58,7 @@ describe('DiscoverSavedSearchContainer', () => {
describe('new', () => {
it('should create a new saved search', async () => {
- const container = getSavedSearchContainer({ services });
+ const container = getSavedSearchContainer({ services, globalStateContainer });
const result = await container.new(dataViewMock);
expect(result.title).toBeUndefined();
@@ -68,7 +71,7 @@ describe('DiscoverSavedSearchContainer', () => {
});
it('should create a new saved search with provided DataView', async () => {
- const container = getSavedSearchContainer({ services });
+ const container = getSavedSearchContainer({ services, globalStateContainer });
const result = await container.new(dataViewMock);
expect(result.title).toBeUndefined();
expect(result.id).toBeUndefined();
@@ -86,6 +89,7 @@ describe('DiscoverSavedSearchContainer', () => {
it('loads a saved search', async () => {
const savedSearchContainer = getSavedSearchContainer({
services: discoverServiceMock,
+ globalStateContainer,
});
await savedSearchContainer.load('the-saved-search-id');
expect(savedSearchContainer.getInitial$().getValue().id).toEqual('the-saved-search-id');
@@ -100,6 +104,7 @@ describe('DiscoverSavedSearchContainer', () => {
it('calls saveSavedSearch with the given saved search and save options', async () => {
const savedSearchContainer = getSavedSearchContainer({
services: discoverServiceMock,
+ globalStateContainer,
});
const savedSearchToPersist = {
...savedSearchMockWithTimeField,
@@ -124,6 +129,7 @@ describe('DiscoverSavedSearchContainer', () => {
const savedSearchContainer = getSavedSearchContainer({
services: discoverServiceMock,
+ globalStateContainer,
});
const result = await savedSearchContainer.persist(persistedSavedSearch, saveOptions);
@@ -135,6 +141,7 @@ describe('DiscoverSavedSearchContainer', () => {
it('emits false to the hasChanged$ BehaviorSubject', async () => {
const savedSearchContainer = getSavedSearchContainer({
services: discoverServiceMock,
+ globalStateContainer,
});
const savedSearchToPersist = {
...savedSearchMockWithTimeField,
@@ -153,6 +160,7 @@ describe('DiscoverSavedSearchContainer', () => {
}));
const savedSearchContainer = getSavedSearchContainer({
services: discoverServiceMock,
+ globalStateContainer,
});
const savedSearchToPersist = {
...savedSearchMockWithTimeField,
@@ -176,6 +184,7 @@ describe('DiscoverSavedSearchContainer', () => {
const savedSearchContainer = getSavedSearchContainer({
services: discoverServiceMock,
+ globalStateContainer,
});
savedSearchContainer.set(savedSearch);
savedSearchContainer.update({ nextState: { hideChart: true } });
@@ -196,28 +205,30 @@ describe('DiscoverSavedSearchContainer', () => {
it('updates a saved search by app state providing hideChart', async () => {
const savedSearchContainer = getSavedSearchContainer({
services: discoverServiceMock,
+ globalStateContainer,
});
savedSearchContainer.set(savedSearch);
- const updated = await savedSearchContainer.update({ nextState: { hideChart: true } });
+ const updated = savedSearchContainer.update({ nextState: { hideChart: true } });
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true);
savedSearchContainer.set(updated);
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(false);
- await savedSearchContainer.update({ nextState: { hideChart: false } });
+ savedSearchContainer.update({ nextState: { hideChart: false } });
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true);
- await savedSearchContainer.update({ nextState: { hideChart: true } });
+ savedSearchContainer.update({ nextState: { hideChart: true } });
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(false);
});
it('updates a saved search by data view', async () => {
const savedSearchContainer = getSavedSearchContainer({
services: discoverServiceMock,
+ globalStateContainer,
});
- const updated = await savedSearchContainer.update({ nextDataView: dataViewMock });
+ const updated = savedSearchContainer.update({ nextDataView: dataViewMock });
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true);
savedSearchContainer.set(updated);
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(false);
- await savedSearchContainer.update({ nextDataView: dataViewComplexMock });
+ savedSearchContainer.update({ nextDataView: dataViewComplexMock });
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true);
- await savedSearchContainer.update({ nextDataView: dataViewMock });
+ savedSearchContainer.update({ nextDataView: dataViewMock });
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(false);
});
});
diff --git a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts
index cad0170cf874d..b47ddd10c4149 100644
--- a/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts
+++ b/src/plugins/discover/public/application/main/services/discover_saved_search_container.ts
@@ -18,6 +18,7 @@ import { handleSourceColumnState } from '../../../utils/state_helpers';
import { DiscoverAppState } from './discover_app_state_container';
import { DiscoverServices } from '../../../build_services';
import { getStateDefaults } from '../utils/get_state_defaults';
+import type { DiscoverGlobalStateContainer } from './discover_global_state_container';
export interface UpdateParams {
/**
@@ -106,8 +107,10 @@ export interface DiscoverSavedSearchContainer {
export function getSavedSearchContainer({
services,
+ globalStateContainer,
}: {
services: DiscoverServices;
+ globalStateContainer: DiscoverGlobalStateContainer;
}): DiscoverSavedSearchContainer {
const initialSavedSearch = services.savedSearch.getNew();
const savedSearchInitial$ = new BehaviorSubject(initialSavedSearch);
@@ -137,6 +140,7 @@ export function getSavedSearchContainer({
savedSearch: { ...nextSavedSearch },
dataView,
state: newAppState,
+ globalStateContainer,
services,
});
return set(nextSavedSearchToSet);
@@ -144,7 +148,12 @@ export function getSavedSearchContainer({
const persist = async (nextSavedSearch: SavedSearch, saveOptions?: SavedObjectSaveOpts) => {
addLog('[savedSearch] persist', { nextSavedSearch, saveOptions });
- updateSavedSearch({ savedSearch: nextSavedSearch, services }, true);
+ updateSavedSearch({
+ savedSearch: nextSavedSearch,
+ globalStateContainer,
+ services,
+ useFilterAndQueryServices: true,
+ });
const id = await services.savedSearch.save(nextSavedSearch, saveOptions || {});
@@ -161,15 +170,14 @@ export function getSavedSearchContainer({
? nextDataView
: previousSavedSearch.searchSource.getField('index')!;
- const nextSavedSearch = updateSavedSearch(
- {
- savedSearch: { ...previousSavedSearch },
- dataView,
- state: nextState || {},
- services,
- },
- useFilterAndQueryServices
- );
+ const nextSavedSearch = updateSavedSearch({
+ savedSearch: { ...previousSavedSearch },
+ dataView,
+ state: nextState || {},
+ globalStateContainer,
+ services,
+ useFilterAndQueryServices,
+ });
const hasChanged = !isEqualSavedSearch(savedSearchInitial$.getValue(), nextSavedSearch);
hasChanged$.next(hasChanged);
diff --git a/src/plugins/discover/public/application/main/services/discover_state.ts b/src/plugins/discover/public/application/main/services/discover_state.ts
index d670878e2f45e..79eea9eed9964 100644
--- a/src/plugins/discover/public/application/main/services/discover_state.ts
+++ b/src/plugins/discover/public/application/main/services/discover_state.ts
@@ -16,7 +16,6 @@ import {
import {
DataPublicPluginStart,
noSearchSessionStorageCapabilityMessage,
- QueryState,
SearchSessionInfoProvider,
} from '@kbn/data-plugin/public';
import { DataView, DataViewSpec, DataViewType } from '@kbn/data-views-plugin/public';
@@ -38,7 +37,6 @@ import {
DiscoverAppState,
DiscoverAppStateContainer,
getDiscoverAppStateContainer,
- GLOBAL_STATE_URL_KEY,
} from './discover_app_state_container';
import {
DiscoverInternalStateContainer,
@@ -51,6 +49,7 @@ import {
DiscoverSavedSearchContainer,
} from './discover_saved_search_container';
import { updateFiltersReferences } from '../utils/update_filter_references';
+import { getDiscoverGlobalStateContainer } from './discover_global_state_container';
interface DiscoverStateContainerParams {
/**
* Browser history
@@ -192,6 +191,7 @@ export function getDiscoverStateContainer({
}: DiscoverStateContainerParams): DiscoverStateContainer {
const storeInSessionStorage = services.uiSettings.get('state:storeInSessionStorage');
const toasts = services.core.notifications.toasts;
+
/**
* state storage for state in the URL
*/
@@ -208,11 +208,18 @@ export function getDiscoverStateContainer({
history,
session: services.data.search.session,
});
+
+ /**
+ * Global State Container, synced with the _g part URL
+ */
+ const globalStateContainer = getDiscoverGlobalStateContainer(stateStorage);
+
/**
* Saved Search State Container, the persisted saved object of Discover
*/
const savedSearchContainer = getSavedSearchContainer({
services,
+ globalStateContainer,
});
/**
@@ -223,6 +230,7 @@ export function getDiscoverStateContainer({
savedSearch: savedSearchContainer.getState(),
services,
});
+
/**
* Internal State Container, state that's not persisted and not part of the URL
*/
@@ -230,13 +238,12 @@ export function getDiscoverStateContainer({
const pauseAutoRefreshInterval = async (dataView: DataView) => {
if (dataView && (!dataView.isTimeBased() || dataView.type === DataViewType.ROLLUP)) {
- const state = stateStorage.get(GLOBAL_STATE_URL_KEY);
+ const state = globalStateContainer.get();
if (state?.refreshInterval && !state.refreshInterval.pause) {
- await stateStorage.set(
- GLOBAL_STATE_URL_KEY,
- { ...state, refreshInterval: { ...state?.refreshInterval, pause: true } },
- { replace: true }
- );
+ await globalStateContainer.set({
+ ...state,
+ refreshInterval: { ...state?.refreshInterval, pause: true },
+ });
}
}
};
@@ -351,8 +358,8 @@ export function getDiscoverStateContainer({
const unsubscribeData = dataStateContainer.subscribe();
// updates saved search when query or filters change, triggers data fetching
- const filterUnsubscribe = merge(services.filterManager.getFetches$()).subscribe(async () => {
- await savedSearchContainer.update({
+ const filterUnsubscribe = merge(services.filterManager.getFetches$()).subscribe(() => {
+ savedSearchContainer.update({
nextDataView: internalStateContainer.getState().dataView,
nextState: appStateContainer.getState(),
useFilterAndQueryServices: true,
@@ -424,7 +431,7 @@ export function getDiscoverStateContainer({
const undoSavedSearchChanges = async () => {
addLog('undoSavedSearchChanges');
const nextSavedSearch = savedSearchContainer.getInitial$().getValue();
- await savedSearchContainer.set(nextSavedSearch);
+ savedSearchContainer.set(nextSavedSearch);
restoreStateFromSavedSearch({
savedSearch: nextSavedSearch,
timefilter: services.timefilter,
diff --git a/src/plugins/discover/public/application/main/utils/update_saved_search.test.ts b/src/plugins/discover/public/application/main/utils/update_saved_search.test.ts
new file mode 100644
index 0000000000000..8c8d48cd9105b
--- /dev/null
+++ b/src/plugins/discover/public/application/main/utils/update_saved_search.test.ts
@@ -0,0 +1,96 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { savedSearchMock } from '../../../__mocks__/saved_search';
+import { discoverServiceMock } from '../../../__mocks__/services';
+import { Filter, FilterStateStore, Query } from '@kbn/es-query';
+import { updateSavedSearch } from './update_saved_search';
+
+describe('updateSavedSearch', () => {
+ const query: Query = {
+ query: 'extension:jpg',
+ language: 'kuery',
+ };
+ const appFilter: Filter = {
+ meta: {},
+ query: {
+ match_phrase: {
+ extension: {
+ query: 'jpg',
+ type: 'phrase',
+ },
+ },
+ },
+ $state: {
+ store: FilterStateStore.APP_STATE,
+ },
+ };
+ const globalFilter: Filter = {
+ meta: {},
+ query: {
+ match_phrase: {
+ extension: {
+ query: 'png',
+ type: 'phrase',
+ },
+ },
+ },
+ $state: {
+ store: FilterStateStore.GLOBAL_STATE,
+ },
+ };
+ const createGlobalStateContainer = () => ({
+ get: jest.fn(() => ({ filters: [globalFilter] })),
+ set: jest.fn(),
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should set query and filters from appState and globalState', async () => {
+ const savedSearch = {
+ ...savedSearchMock,
+ searchSource: savedSearchMock.searchSource.createCopy(),
+ };
+ expect(savedSearch.searchSource.getField('query')).toBeUndefined();
+ expect(savedSearch.searchSource.getField('filter')).toBeUndefined();
+ updateSavedSearch({
+ savedSearch,
+ globalStateContainer: createGlobalStateContainer(),
+ services: discoverServiceMock,
+ state: {
+ query,
+ filters: [appFilter],
+ },
+ });
+ expect(savedSearch.searchSource.getField('query')).toEqual(query);
+ expect(savedSearch.searchSource.getField('filter')).toEqual([appFilter, globalFilter]);
+ });
+
+ it('should set query and filters from services', async () => {
+ const savedSearch = {
+ ...savedSearchMock,
+ searchSource: savedSearchMock.searchSource.createCopy(),
+ };
+ expect(savedSearch.searchSource.getField('query')).toBeUndefined();
+ expect(savedSearch.searchSource.getField('filter')).toBeUndefined();
+ jest
+ .spyOn(discoverServiceMock.data.query.filterManager, 'getFilters')
+ .mockReturnValue([appFilter, globalFilter]);
+ jest.spyOn(discoverServiceMock.data.query.queryString, 'getQuery').mockReturnValue(query);
+ updateSavedSearch({
+ savedSearch,
+ globalStateContainer: createGlobalStateContainer(),
+ services: discoverServiceMock,
+ useFilterAndQueryServices: true,
+ });
+ expect(savedSearch.searchSource.getField('query')).toEqual(query);
+ expect(savedSearch.searchSource.getField('filter')).toEqual([appFilter, globalFilter]);
+ });
+});
diff --git a/src/plugins/discover/public/application/main/utils/update_saved_search.ts b/src/plugins/discover/public/application/main/utils/update_saved_search.ts
index 733bf5ed2a0b8..dc5e5307f5fe0 100644
--- a/src/plugins/discover/public/application/main/utils/update_saved_search.ts
+++ b/src/plugins/discover/public/application/main/utils/update_saved_search.ts
@@ -5,12 +5,13 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
-import { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public';
-import { DataView } from '@kbn/data-views-plugin/common';
+import type { SavedSearch, SortOrder } from '@kbn/saved-search-plugin/public';
+import type { DataView } from '@kbn/data-views-plugin/common';
import { cloneDeep } from 'lodash';
import { isTextBasedQuery } from './is_text_based_query';
-import { DiscoverAppState } from '../services/discover_app_state_container';
-import { DiscoverServices } from '../../../build_services';
+import type { DiscoverAppState } from '../services/discover_app_state_container';
+import type { DiscoverServices } from '../../../build_services';
+import type { DiscoverGlobalStateContainer } from '../services/discover_global_state_container';
/**
* Updates the saved search with a given data view & Appstate
@@ -22,20 +23,21 @@ import { DiscoverServices } from '../../../build_services';
* @param services
* @param useFilterAndQueryServices - when true data services are being used for updating filter + query
*/
-export function updateSavedSearch(
- {
- savedSearch,
- dataView,
- state,
- services,
- }: {
- savedSearch: SavedSearch;
- dataView?: DataView;
- state?: DiscoverAppState;
- services: DiscoverServices;
- },
- useFilterAndQueryServices: boolean = false
-) {
+export function updateSavedSearch({
+ savedSearch,
+ dataView,
+ state,
+ globalStateContainer,
+ services,
+ useFilterAndQueryServices = false,
+}: {
+ savedSearch: SavedSearch;
+ dataView?: DataView;
+ state?: DiscoverAppState;
+ globalStateContainer: DiscoverGlobalStateContainer;
+ services: DiscoverServices;
+ useFilterAndQueryServices?: boolean;
+}) {
if (dataView) {
savedSearch.searchSource.setField('index', dataView);
savedSearch.usesAdHocDataView = !dataView.isPersisted();
@@ -45,9 +47,12 @@ export function updateSavedSearch(
.setField('query', services.data.query.queryString.getQuery())
.setField('filter', services.data.query.filterManager.getFilters());
} else if (state) {
+ const appFilters = state.filters ? cloneDeep(state.filters) : [];
+ const globalFilters = globalStateContainer.get()?.filters ?? [];
+
savedSearch.searchSource
.setField('query', state.query ?? undefined)
- .setField('filter', state.filters ? cloneDeep(state.filters) : []);
+ .setField('filter', [...appFilters, ...globalFilters]);
}
if (state) {
savedSearch.columns = state.columns || [];
diff --git a/src/plugins/unified_search/public/filters_builder/filter_group.tsx b/src/plugins/unified_search/public/filters_builder/filter_group.tsx
index cad3cee7a9063..73813e512a229 100644
--- a/src/plugins/unified_search/public/filters_builder/filter_group.tsx
+++ b/src/plugins/unified_search/public/filters_builder/filter_group.tsx
@@ -81,7 +81,7 @@ export const FilterGroup = ({
} = useContext(FiltersBuilderContextType);
const pathInArray = getPathInArray(path);
- const isDepthReached = maxDepth <= pathInArray.length;
+ const isDepthReached = maxDepth <= pathInArray.length && renderedLevel > 0;
const orDisabled = hideOr || (isDepthReached && booleanRelation === BooleanRelation.AND);
const andDisabled = isDepthReached && booleanRelation === BooleanRelation.OR;
diff --git a/src/plugins/visualization_ui_components/public/components/color_picker.tsx b/src/plugins/visualization_ui_components/public/components/color_picker.tsx
index 9bf2f57305619..74c17b76ba2f1 100644
--- a/src/plugins/visualization_ui_components/public/components/color_picker.tsx
+++ b/src/plugins/visualization_ui_components/public/components/color_picker.tsx
@@ -26,19 +26,15 @@ const tooltipContent = {
custom: i18n.translate('visualizationUiComponents.colorPicker.tooltip.custom', {
defaultMessage: 'Clear the custom color to return to “Auto” mode.',
}),
- disabled: i18n.translate('visualizationUiComponents.colorPicker.tooltip.disabled', {
- defaultMessage:
- 'You are unable to apply custom colors to individual series when the layer includes a "Break down by" field.',
- }),
};
export const ColorPicker = ({
+ overwriteColor,
+ defaultColor,
+ setConfig,
label,
disableHelpTooltip,
- disabled,
- setConfig,
- defaultColor,
- overwriteColor,
+ disabledMessage,
showAlpha,
}: {
overwriteColor?: string | null;
@@ -46,7 +42,7 @@ export const ColorPicker = ({
setConfig: (config: { color?: string }) => void;
label?: string;
disableHelpTooltip?: boolean;
- disabled?: boolean;
+ disabledMessage?: string;
showAlpha?: boolean;
}) => {
const [colorText, setColorText] = useState(overwriteColor || defaultColor);
@@ -54,6 +50,8 @@ export const ColorPicker = ({
const [currentColorAlpha, setCurrentColorAlpha] = useState(getColorAlpha(colorText));
const unflushedChanges = useRef(false);
+ const isDisabled = Boolean(disabledMessage);
+
useEffect(() => {
// only the changes from outside the color picker should be applied
if (!unflushedChanges.current) {
@@ -97,8 +95,8 @@ export const ColorPicker = ({
compressed
isClearable={Boolean(overwriteColor)}
onChange={handleColor}
- color={disabled ? '' : colorText}
- disabled={disabled}
+ color={isDisabled ? '' : colorText}
+ disabled={isDisabled}
placeholder={
defaultColor?.toUpperCase() ||
i18n.translate('visualizationUiComponents.colorPicker.seriesColor.auto', {
@@ -123,7 +121,7 @@ export const ColorPicker = ({
@@ -142,10 +140,10 @@ export const ColorPicker = ({
}
>
- {disabled ? (
+ {isDisabled ? (
diff --git a/test/accessibility/apps/dashboard.ts b/test/accessibility/apps/dashboard.ts
index 37c2bb2ba4586..8c2924a0901c1 100644
--- a/test/accessibility/apps/dashboard.ts
+++ b/test/accessibility/apps/dashboard.ts
@@ -148,7 +148,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await listingTable.clickDeleteSelected();
await a11y.testAppSnapshot();
await PageObjects.common.clickConfirmOnModal();
- await listingTable.searchForItemWithName('');
+ await listingTable.isShowingEmptyPromptCreateNewButton();
});
});
}
diff --git a/test/api_integration/apis/data_views/has_user_index_pattern/has_user_index_pattern.ts b/test/api_integration/apis/data_views/has_user_index_pattern/has_user_index_pattern.ts
index 18bd36c1e6258..cad10b122a927 100644
--- a/test/api_integration/apis/data_views/has_user_index_pattern/has_user_index_pattern.ts
+++ b/test/api_integration/apis/data_views/has_user_index_pattern/has_user_index_pattern.ts
@@ -6,6 +6,11 @@
* Side Public License, v 1.
*/
+import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
+import {
+ INITIAL_REST_VERSION,
+ INITIAL_REST_VERSION_INTERNAL,
+} from '@kbn/data-views-plugin/server/constants';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { configArray } from '../constants';
@@ -15,8 +20,7 @@ export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const es = getService('es');
- // FLAKY: https://github.com/elastic/kibana/issues/156129
- describe.skip('has user index pattern API', () => {
+ describe('has user index pattern API', () => {
configArray.forEach((config) => {
describe(config.name, () => {
beforeEach(async () => {
@@ -33,7 +37,11 @@ export default function ({ getService }: FtrProviderContext) {
const servicePath = `${config.basePath}/has_user_${config.serviceKey}`;
it('should return false if no index patterns', async () => {
- const response = await supertest.get(servicePath);
+ // Make sure all saved objects including data views are cleared
+ await esArchiver.emptyKibanaIndex();
+ const response = await supertest
+ .get(servicePath)
+ .set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION_INTERNAL);
expect(response.status).to.be(200);
expect(response.body.result).to.be(false);
});
@@ -42,14 +50,19 @@ export default function ({ getService }: FtrProviderContext) {
await esArchiver.load(
'test/api_integration/fixtures/es_archiver/index_patterns/basic_index'
);
- await supertest.post(config.path).send({
- override: true,
- [config.serviceKey]: {
- title: 'basic_index',
- },
- });
+ await supertest
+ .post(config.path)
+ .set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
+ .send({
+ override: true,
+ [config.serviceKey]: {
+ title: 'basic_index',
+ },
+ });
- const response = await supertest.get(servicePath);
+ const response = await supertest
+ .get(servicePath)
+ .set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION_INTERNAL);
expect(response.status).to.be(200);
expect(response.body.result).to.be(true);
@@ -59,15 +72,20 @@ export default function ({ getService }: FtrProviderContext) {
});
it('should return true if has user index pattern without data', async () => {
- await supertest.post(config.path).send({
- override: true,
- [config.serviceKey]: {
- title: 'basic_index',
- allowNoIndex: true,
- },
- });
+ await supertest
+ .post(config.path)
+ .set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
+ .send({
+ override: true,
+ [config.serviceKey]: {
+ title: 'basic_index',
+ allowNoIndex: true,
+ },
+ });
- const response = await supertest.get(servicePath);
+ const response = await supertest
+ .get(servicePath)
+ .set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION_INTERNAL);
expect(response.status).to.be(200);
expect(response.body.result).to.be(true);
});
diff --git a/test/api_integration/apis/saved_objects/find.ts b/test/api_integration/apis/saved_objects/find.ts
index 6d4aae2ad561f..e5ae6c674ba8d 100644
--- a/test/api_integration/apis/saved_objects/find.ts
+++ b/test/api_integration/apis/saved_objects/find.ts
@@ -6,6 +6,7 @@
* Side Public License, v 1.
*/
+import { sortBy } from 'lodash';
import { MAIN_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server';
import expect from '@kbn/expect';
import { SavedObject } from '@kbn/core/server';
@@ -153,8 +154,7 @@ export default function ({ getService }: FtrProviderContext) {
}));
});
- // FLAKY: https://github.com/elastic/kibana/issues/156581
- describe.skip('wildcard namespace', () => {
+ describe('wildcard namespace', () => {
it('should return 200 with individual responses from the all namespaces', async () =>
await supertest
.get(
@@ -165,7 +165,8 @@ export default function ({ getService }: FtrProviderContext) {
const knownDocuments = resp.body.saved_objects.filter((so: { namespaces: string[] }) =>
so.namespaces.some((ns) => [SPACE_ID, `${SPACE_ID}-foo`].includes(ns))
);
- const [obj1, obj2] = knownDocuments.map(
+
+ const [obj1, obj2] = sortBy(knownDocuments, 'namespaces').map(
({ id, originId, namespaces }: SavedObject) => ({ id, originId, namespaces })
);
diff --git a/test/functional/apps/console/_variables.ts b/test/functional/apps/console/_variables.ts
index 70fe54517ea8d..8e09460b4dc6c 100644
--- a/test/functional/apps/console/_variables.ts
+++ b/test/functional/apps/console/_variables.ts
@@ -14,9 +14,9 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
const log = getService('log');
const PageObjects = getPageObjects(['common', 'console', 'header']);
- // Failing: See https://github.com/elastic/kibana/issues/157776
- describe.skip('Console variables', function testConsoleVariables() {
- this.tags('includeFirefox');
+ describe('Console variables', function testConsoleVariables() {
+ // FLAKY on firefox: https://github.com/elastic/kibana/issues/157776
+ // this.tags('includeFirefox');
before(async () => {
log.debug('navigateTo console');
await PageObjects.common.navigateToApp('console');
diff --git a/test/functional/apps/discover/group1/_filter_editor.ts b/test/functional/apps/discover/group1/_filter_editor.ts
index 1f212a5dd8d63..fdc21921833f4 100644
--- a/test/functional/apps/discover/group1/_filter_editor.ts
+++ b/test/functional/apps/discover/group1/_filter_editor.ts
@@ -17,7 +17,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const kibanaServer = getService('kibanaServer');
const filterBar = getService('filterBar');
const security = getService('security');
- const PageObjects = getPageObjects(['common', 'discover', 'timePicker']);
+ const browser = getService('browser');
+ const dataGrid = getService('dataGrid');
+ const PageObjects = getPageObjects(['common', 'discover', 'timePicker', 'unifiedFieldList']);
const defaultSettings = {
defaultIndex: 'logstash-*',
};
@@ -124,6 +126,45 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
});
});
+
+ const runFilterTest = async (pinned = false) => {
+ await filterBar.removeAllFilters();
+ await PageObjects.unifiedFieldList.clickFieldListItemAdd('extension');
+ await retry.try(async function () {
+ const cell = await dataGrid.getCellElement(0, 3);
+ expect(await cell.getVisibleText()).to.be('jpg');
+ expect(await PageObjects.discover.getHitCount()).to.be('14,004');
+ });
+ await filterBar.addFilter({
+ field: 'extension.raw',
+ operation: 'is',
+ value: 'css',
+ });
+ if (pinned) {
+ await filterBar.toggleFilterPinned('extension.raw');
+ }
+ await retry.try(async function () {
+ expect(await filterBar.hasFilter('extension.raw', 'css', true, pinned)).to.be(true);
+ const cell = await dataGrid.getCellElement(0, 3);
+ expect(await cell.getVisibleText()).to.be('css');
+ expect(await PageObjects.discover.getHitCount()).to.be('2,159');
+ });
+ await browser.refresh();
+ await retry.try(async function () {
+ expect(await filterBar.hasFilter('extension.raw', 'css', true, pinned)).to.be(true);
+ expect(await PageObjects.discover.getHitCount()).to.be('2,159');
+ const cell = await dataGrid.getCellElement(0, 3);
+ expect(await cell.getVisibleText()).to.be('css');
+ });
+ };
+
+ it('should support app filters in histogram/total hits and data grid', async function () {
+ await runFilterTest();
+ });
+
+ it('should support pinned filters in histogram/total hits and data grid', async function () {
+ await runFilterTest(true);
+ });
});
after(async () => {
diff --git a/test/functional/apps/visualize/group5/_tsvb_time_series.ts b/test/functional/apps/visualize/group5/_tsvb_time_series.ts
index 81b653630159f..e344f7d9f8dd7 100644
--- a/test/functional/apps/visualize/group5/_tsvb_time_series.ts
+++ b/test/functional/apps/visualize/group5/_tsvb_time_series.ts
@@ -11,14 +11,16 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
- const { visualize, visualBuilder, timeToVisualize, dashboard, header, common } = getPageObjects([
- 'visualBuilder',
- 'visualize',
- 'timeToVisualize',
- 'dashboard',
- 'header',
- 'common',
- ]);
+ const { visualize, visualBuilder, timeToVisualize, dashboard, header, common, visChart } =
+ getPageObjects([
+ 'visualBuilder',
+ 'visualize',
+ 'timeToVisualize',
+ 'dashboard',
+ 'header',
+ 'common',
+ 'visChart',
+ ]);
const security = getService('security');
const testSubjects = getService('testSubjects');
const retry = getService('retry');
@@ -28,8 +30,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const browser = getService('browser');
const kibanaServer = getService('kibanaServer');
- // Failing: See https://github.com/elastic/kibana/issues/158972
- describe.skip('visual builder', function describeIndexTests() {
+ describe('visual builder', function describeIndexTests() {
before(async () => {
await security.testUser.setRoles([
'kibana_admin',
@@ -223,11 +224,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
it('should create a filter for series with multiple split by terms fields one of which has formatting', async () => {
const expectedFilterPills = ['0, win 7'];
await visualBuilder.setMetricsGroupByTerms('bytes');
+ await visChart.waitForVisualizationRenderingStabilized();
await header.waitUntilLoadingHasFinished();
await visualBuilder.setAnotherGroupByTermsField('machine.os.raw');
+ await visChart.waitForVisualizationRenderingStabilized();
await header.waitUntilLoadingHasFinished();
await visualBuilder.clickSeriesOption();
await visualBuilder.setChartType('Bar');
+ await visChart.waitForVisualizationRenderingStabilized();
await header.waitUntilLoadingHasFinished();
await visualBuilder.clickPanelOptions('timeSeries');
await visualBuilder.setIntervalValue('1w');
diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts
index 9bd2eb80132a8..414e8147e66ea 100644
--- a/test/functional/services/listing_table.ts
+++ b/test/functional/services/listing_table.ts
@@ -256,6 +256,10 @@ export class ListingTableService extends FtrService {
await this.testSubjects.click('newItemButton');
}
+ public async isShowingEmptyPromptCreateNewButton(): Promise {
+ await this.testSubjects.existOrFail('newItemButton');
+ }
+
public async onListingPage(appName: AppName) {
return await this.testSubjects.exists(`${appName}LandingPage`, {
timeout: 5000,
diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx
index 91200fd57ceb7..2f75406439e2b 100644
--- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx
+++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/assistant_overlay/index.tsx
@@ -18,11 +18,10 @@ import { WELCOME_CONVERSATION_TITLE } from '../use_conversation/translations';
const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
const StyledEuiModal = styled(EuiModal)`
- min-width: 1200px;
- max-height: 100%;
- height: 100%;
+ ${({ theme }) => `margin-top: ${theme.eui.euiSizeXXL};`}
+ min-width: 95vw;
+ min-height: 25vh;
`;
-
/**
* Modal container for Elastic AI Assistant conversations, receiving the page contents as context, plus whatever
* component currently has focus and any specific context it may provide through the SAssInterface.
@@ -79,7 +78,7 @@ export const AssistantOverlay: React.FC = React.memo(() => {
return (
<>
{isModalVisible && (
-
+
)}
diff --git a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx
index 6ff45eadcaaa3..6e2765816b9de 100644
--- a/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx
+++ b/x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx
@@ -14,16 +14,16 @@ import {
EuiHorizontalRule,
EuiCommentList,
EuiToolTip,
- EuiSplitPanel,
EuiSwitchEvent,
EuiSwitch,
EuiCallOut,
EuiIcon,
- EuiTitle,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalBody,
+ EuiModalHeaderTitle,
} from '@elastic/eui';
-// eslint-disable-next-line @kbn/eslint/module_migration
-import styled from 'styled-components';
import { createPortal } from 'react-dom';
import { css } from '@emotion/react';
@@ -48,26 +48,10 @@ import { getCombinedMessage } from './prompt/helpers';
import * as i18n from './translations';
import { QuickPrompts } from './quick_prompts/quick_prompts';
import { useLoadConnectors } from '../connectorland/use_load_connectors';
-import { ConnectorSetup } from '../connectorland/connector_setup';
+import { useConnectorSetup } from '../connectorland/connector_setup';
import { WELCOME_CONVERSATION_TITLE } from './use_conversation/translations';
import { BASE_CONVERSATIONS } from './use_conversation/sample_conversations';
-const CommentsContainer = styled.div`
- max-height: 600px;
- max-width: 100%;
- overflow-y: scroll;
-`;
-
-const ChatOptionsFlexItem = styled(EuiFlexItem)`
- left: -34px;
- position: relative;
- top: 11px;
-`;
-
-const StyledCommentList = styled(EuiCommentList)`
- margin-right: 20px;
-`;
-
export interface Props {
promptContextId?: string;
conversationId?: string;
@@ -131,6 +115,13 @@ const AssistantComponent: React.FC = ({
);
const isWelcomeSetup = (connectors?.length ?? 0) === 0;
+
+ const { connectorDialog, connectorPrompt } = useConnectorSetup({
+ actionTypeRegistry,
+ http,
+ refetchConnectors,
+ isConnectorConfigured: !!connectors?.length,
+ });
const currentTitle: { title: string | JSX.Element; titleIcon: string } =
isWelcomeSetup && welcomeConversation.theme?.title && welcomeConversation.theme?.titleIcon
? { title: welcomeConversation.theme?.title, titleIcon: welcomeConversation.theme?.titleIcon }
@@ -336,29 +327,49 @@ const AssistantComponent: React.FC = ({
setShowMissingConnectorCallout(!connectorExists);
}, [connectors, currentConversation]);
+ const CodeBlockPortals = useMemo(
+ () =>
+ messageCodeBlocks.map((codeBlocks: CodeBlockDetails[]) => {
+ return codeBlocks.map((codeBlock: CodeBlockDetails) => {
+ const element: Element = codeBlock.controlContainer as Element;
+
+ return codeBlock.controlContainer != null ? (
+ createPortal(codeBlock.button, element)
+ ) : (
+ <>>
+ );
+ });
+ }),
+ [messageCodeBlocks]
+ );
return (
-
-
+ <>
+
{showTitle && (
<>
-
+
-
-
-
-
-
-
- {currentTitle.title}
-
-
-
+
+
+
+
+
+ {currentTitle.title}
+
+
+
= ({
)}
{/* Create portals for each EuiCodeBlock to add the `Investigate in Timeline` action */}
- {messageCodeBlocks.map((codeBlocks: CodeBlockDetails[]) => {
- return codeBlocks.map((codeBlock: CodeBlockDetails) => {
- const element: Element = codeBlock.controlContainer as Element;
-
- return codeBlock.controlContainer != null ? (
- createPortal(codeBlock.button, element)
- ) : (
- <>>
- );
- });
- })}
+ {CodeBlockPortals}
{!isWelcomeSetup && (
<>
@@ -446,43 +447,59 @@ const AssistantComponent: React.FC = ({
{Object.keys(promptContexts).length > 0 && }
>
)}
+
+
+ {isWelcomeSetup ? (
+ connectorDialog
+ ) : (
+ <>
+
- {isWelcomeSetup && (
-
- )}
-
- {!isWelcomeSetup && (
-
- <>
-
-
-
-
- {(currentConversation.messages.length === 0 ||
- Object.keys(selectedPromptContexts).length > 0) && (
-
- )}
+
+
+ {(currentConversation.messages.length === 0 ||
+ Object.keys(selectedPromptContexts).length > 0) && (
+
+ )}
-
- >
-
+
+ >
)}
-
-
+
+
+
+ {isWelcomeSetup && {connectorPrompt}}
+
+
= ({
/>
-
+
= ({
/>
-
+
-
- {!isWelcomeSetup && (
-
-
-
- )}
-
+ {!isWelcomeSetup && }
+
+ >
);
};
diff --git a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx
index 6229419a397cf..66c58e2b9ac61 100644
--- a/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx
+++ b/x-pack/packages/kbn-elastic-assistant/impl/connectorland/connector_setup/index.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useCallback, useRef, useState } from 'react';
+import React, { useCallback, useMemo, useRef, useState } from 'react';
import type { EuiCommentProps } from '@elastic/eui';
import {
EuiAvatar,
@@ -21,7 +21,7 @@ import { ConnectorAddModal } from '@kbn/triggers-actions-ui-plugin/public/common
import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public';
import { HttpSetup } from '@kbn/core-http-browser';
-import { ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public';
+import { ActionType, ActionTypeRegistryContract } from '@kbn/triggers-actions-ui-plugin/public';
import {
GEN_AI_CONNECTOR_ID,
OpenAiProviderType,
@@ -39,11 +39,6 @@ import { WELCOME_CONVERSATION_TITLE } from '../../assistant/use_conversation/tra
const MESSAGE_INDEX_BEFORE_CONNECTOR = 2;
-const CommentsContainer = styled.div`
- max-height: 600px;
- overflow-y: scroll;
-`;
-
const StyledCommentList = styled(EuiCommentList)`
margin-right: 20px;
`;
@@ -69,137 +64,136 @@ export interface ConnectorSetupProps {
refetchConnectors?: () => void;
}
-export const ConnectorSetup: React.FC = React.memo(
- ({
- actionTypeRegistry,
- conversation = BASE_CONVERSATIONS[WELCOME_CONVERSATION_TITLE],
- http,
- isConnectorConfigured = false,
- onSetupComplete,
- refetchConnectors,
- }) => {
- const { appendMessage, setApiConfig, setConversation } = useConversation();
- const lastCommentRef = useRef(null);
- const bottomRef = useRef(null);
-
- // Access all conversations so we can add connector to all on initial setup
- const { conversations } = useAssistantContext();
-
- const [isConnectorModalVisible, setIsConnectorModalVisible] = useState(false);
- const [showAddConnectorButton, setShowAddConnectorButton] = useState(() => {
- // If no presentation data on messages, default to showing add connector button so it doesn't delay render and flash on screen
- return conversationHasNoPresentationData(conversation);
- });
- const { data: actionTypes } = useLoadActionTypes({ http });
-
- const actionType = actionTypes?.find((at) => at.id === GEN_AI_CONNECTOR_ID) ?? {
- enabledInConfig: true,
- enabledInLicense: true,
- minimumLicenseRequired: 'platinum',
- supportedFeatureIds: ['general'],
- id: '.gen-ai',
- name: 'Generative AI',
- enabled: true,
- };
-
- // User constants
- const userName = conversation.theme?.user?.name ?? i18n.CONNECTOR_SETUP_USER_YOU;
- const assistantName =
- conversation.theme?.assistant?.name ?? i18n.CONNECTOR_SETUP_USER_ASSISTANT;
-
- const [currentMessageIndex, setCurrentMessageIndex] = useState(
- // If connector is configured or conversation has already been replayed show all messages immediately
- isConnectorConfigured || conversationHasNoPresentationData(conversation)
- ? MESSAGE_INDEX_BEFORE_CONNECTOR
- : 0
- );
-
- // Once streaming of previous message is complete, proceed to next message
- const onHandleMessageStreamingComplete = useCallback(() => {
- const timeoutId = setTimeout(
- () => setCurrentMessageIndex(currentMessageIndex + 1),
- conversation.messages[currentMessageIndex].presentation?.delay ?? 0
- );
-
- return () => clearTimeout(timeoutId);
- }, [conversation.messages, currentMessageIndex]);
-
- // Show button to add connector after last message has finished streaming
- const onHandleLastMessageStreamingComplete = useCallback(() => {
- setShowAddConnectorButton(true);
- onSetupComplete?.();
- setConversation({ conversation: clearPresentationData(conversation) });
- }, [conversation, onSetupComplete, setConversation]);
-
- // Show button to add connector after last message has finished streaming
- const handleSkipSetup = useCallback(() => {
- setCurrentMessageIndex(MESSAGE_INDEX_BEFORE_CONNECTOR);
- }, [setCurrentMessageIndex]);
-
- // Create EuiCommentProps[] from conversation messages
- const commentBody = useCallback(
- (message: Message, index: number, length: number) => {
- // If timestamp is not set, set it to current time (will update conversation at end of setup)
- if (conversation.messages[index].timestamp.length === 0) {
- conversation.messages[index].timestamp = new Date().toLocaleString();
- }
- const isLastMessage = index === length - 1;
- const enableStreaming =
- (message.presentation?.stream ?? false) && currentMessageIndex !== length - 1;
- return (
-
- {(streamedText, isStreamingComplete) => (
-
- {streamedText}
- {isLastMessage && isStreamingComplete && }
-
- )}
-
- );
+export const useConnectorSetup = ({
+ actionTypeRegistry,
+ conversation = BASE_CONVERSATIONS[WELCOME_CONVERSATION_TITLE],
+ http,
+ isConnectorConfigured = false,
+ onSetupComplete,
+ refetchConnectors,
+}: ConnectorSetupProps): {
+ connectorDialog: React.ReactElement;
+ connectorPrompt: React.ReactElement;
+} => {
+ const { appendMessage, setApiConfig, setConversation } = useConversation();
+ const lastCommentRef = useRef(null);
+
+ // Access all conversations so we can add connector to all on initial setup
+ const { conversations } = useAssistantContext();
+
+ const [isConnectorModalVisible, setIsConnectorModalVisible] = useState(false);
+ const [showAddConnectorButton, setShowAddConnectorButton] = useState(() => {
+ // If no presentation data on messages, default to showing add connector button so it doesn't delay render and flash on screen
+ return conversationHasNoPresentationData(conversation);
+ });
+ const { data: actionTypes } = useLoadActionTypes({ http });
+ const actionType: ActionType = useMemo(
+ () =>
+ actionTypes?.find((at) => at.id === GEN_AI_CONNECTOR_ID) ?? {
+ enabledInConfig: true,
+ enabledInLicense: true,
+ minimumLicenseRequired: 'platinum',
+ supportedFeatureIds: ['general'],
+ id: '.gen-ai',
+ name: 'Generative AI',
+ enabled: true,
},
- [
- conversation.messages,
- currentMessageIndex,
- onHandleLastMessageStreamingComplete,
- onHandleMessageStreamingComplete,
- ]
+ [actionTypes]
+ );
+
+ // User constants
+ const userName = conversation.theme?.user?.name ?? i18n.CONNECTOR_SETUP_USER_YOU;
+ const assistantName = conversation.theme?.assistant?.name ?? i18n.CONNECTOR_SETUP_USER_ASSISTANT;
+
+ const [currentMessageIndex, setCurrentMessageIndex] = useState(
+ // If connector is configured or conversation has already been replayed show all messages immediately
+ isConnectorConfigured || conversationHasNoPresentationData(conversation)
+ ? MESSAGE_INDEX_BEFORE_CONNECTOR
+ : 0
+ );
+
+ // Once streaming of previous message is complete, proceed to next message
+ const onHandleMessageStreamingComplete = useCallback(() => {
+ const timeoutId = setTimeout(
+ () => setCurrentMessageIndex(currentMessageIndex + 1),
+ conversation.messages[currentMessageIndex].presentation?.delay ?? 0
);
- return (
- <>
-
- {
- const isUser = message.role === 'user';
-
- const commentProps: EuiCommentProps = {
- username: isUser ? userName : assistantName,
- children: commentBody(message, index, conversation.messages.length),
- timelineAvatar: (
-
- ),
- timestamp: `${i18n.CONNECTOR_SETUP_TIMESTAMP_AT}: ${message.timestamp}`,
- };
- return commentProps;
- })}
- />
-
-
+ return () => clearTimeout(timeoutId);
+ }, [conversation.messages, currentMessageIndex]);
+
+ // Show button to add connector after last message has finished streaming
+ const onHandleLastMessageStreamingComplete = useCallback(() => {
+ setShowAddConnectorButton(true);
+ onSetupComplete?.();
+ setConversation({ conversation: clearPresentationData(conversation) });
+ }, [conversation, onSetupComplete, setConversation]);
+
+ // Show button to add connector after last message has finished streaming
+ const handleSkipSetup = useCallback(() => {
+ setCurrentMessageIndex(MESSAGE_INDEX_BEFORE_CONNECTOR);
+ }, [setCurrentMessageIndex]);
+
+ // Create EuiCommentProps[] from conversation messages
+ const commentBody = useCallback(
+ (message: Message, index: number, length: number) => {
+ // If timestamp is not set, set it to current time (will update conversation at end of setup)
+ if (conversation.messages[index].timestamp.length === 0) {
+ conversation.messages[index].timestamp = new Date().toLocaleString();
+ }
+ const isLastMessage = index === length - 1;
+ const enableStreaming =
+ (message.presentation?.stream ?? false) && currentMessageIndex !== length - 1;
+ return (
+
+ {(streamedText, isStreamingComplete) => (
+
+ {streamedText}
+ {isLastMessage && isStreamingComplete && }
+
+ )}
+
+ );
+ },
+ [
+ conversation.messages,
+ currentMessageIndex,
+ onHandleLastMessageStreamingComplete,
+ onHandleMessageStreamingComplete,
+ ]
+ );
+
+ return {
+ connectorDialog: (
+ {
+ const isUser = message.role === 'user';
+
+ const commentProps: EuiCommentProps = {
+ username: isUser ? userName : assistantName,
+ children: commentBody(message, index, conversation.messages.length),
+ timelineAvatar: (
+
+ ),
+ timestamp: `${i18n.CONNECTOR_SETUP_TIMESTAMP_AT}: ${message.timestamp}`,
+ };
+ return commentProps;
+ })}
+ />
+ ),
+ connectorPrompt: (
+
{(showAddConnectorButton || isConnectorConfigured) && (
= React.memo
)}
-
{isConnectorModalVisible && (
= React.memo
)}
- >
- );
- }
-);
-ConnectorSetup.displayName = 'ConnectorSetup';
+
+ ),
+ };
+};
diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts
index 85fbb547408b1..2eacd29a39db6 100644
--- a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts
+++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts
@@ -274,8 +274,6 @@ describe('Execution Handler', () => {
});
expect(jest.requireMock('./inject_action_params').injectActionParams).toHaveBeenCalledWith({
- ruleId: '1',
- spaceId: 'test1',
actionTypeId: 'test',
actionParams: {
alertVal: 'My 1 name-of-alert test1 tag-A,tag-B 1 goes here',
@@ -1642,8 +1640,7 @@ describe('Execution Handler', () => {
"val": "rule url: http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1",
},
"actionTypeId": "test",
- "ruleId": "1",
- "spaceId": "test1",
+ "ruleUrl": "http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1",
},
]
`);
@@ -1709,8 +1706,7 @@ describe('Execution Handler', () => {
"val": "rule url: http://localhost:12345/s/test1/app/test/rule/1?start=30000&end=90000",
},
"actionTypeId": "test",
- "ruleId": "1",
- "spaceId": "test1",
+ "ruleUrl": "http://localhost:12345/s/test1/app/test/rule/1?start=30000&end=90000",
},
]
`);
@@ -1739,8 +1735,7 @@ describe('Execution Handler', () => {
"val": "rule url: http://localhost:12345/app/management/insightsAndAlerting/triggersActions/rule/1",
},
"actionTypeId": "test",
- "ruleId": "1",
- "spaceId": "default",
+ "ruleUrl": "http://localhost:12345/app/management/insightsAndAlerting/triggersActions/rule/1",
},
]
`);
@@ -1766,8 +1761,7 @@ describe('Execution Handler', () => {
"val": "rule url: http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1",
},
"actionTypeId": "test",
- "ruleId": "1",
- "spaceId": "test1",
+ "ruleUrl": "http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1",
},
]
`);
@@ -1793,8 +1787,7 @@ describe('Execution Handler', () => {
"val": "rule url: ",
},
"actionTypeId": "test",
- "ruleId": "1",
- "spaceId": "test1",
+ "ruleUrl": undefined,
},
]
`);
@@ -1823,8 +1816,7 @@ describe('Execution Handler', () => {
"val": "rule url: ",
},
"actionTypeId": "test",
- "ruleId": "1",
- "spaceId": "test1",
+ "ruleUrl": undefined,
},
]
`);
@@ -1853,8 +1845,7 @@ describe('Execution Handler', () => {
"val": "rule url: ",
},
"actionTypeId": "test",
- "ruleId": "1",
- "spaceId": "test1",
+ "ruleUrl": undefined,
},
]
`);
@@ -1886,8 +1877,7 @@ describe('Execution Handler', () => {
"val": "rule url: http://localhost:12345/s/test1/app/management/some/other/place",
},
"actionTypeId": "test",
- "ruleId": "1",
- "spaceId": "test1",
+ "ruleUrl": "http://localhost:12345/s/test1/app/management/some/other/place",
},
]
`);
diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts
index b19e31b0c97d2..d091608c10f4f 100644
--- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts
+++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts
@@ -220,12 +220,12 @@ export class ExecutionHandler<
this.rule.schedule,
this.previousStartedAt
);
+ const ruleUrl = this.buildRuleUrl(spaceId, start, end);
const actionToRun = {
...action,
params: injectActionParams({
- ruleId,
- spaceId,
actionTypeId,
+ ruleUrl,
actionParams: transformSummaryActionParams({
alerts: summarizedAlerts,
rule: this.rule,
@@ -236,7 +236,7 @@ export class ExecutionHandler<
actionsPlugin,
actionTypeId,
kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl,
- ruleUrl: this.buildRuleUrl(spaceId, start, end),
+ ruleUrl,
}),
}),
};
@@ -261,12 +261,12 @@ export class ExecutionHandler<
});
} else {
const executableAlert = alert!;
+ const ruleUrl = this.buildRuleUrl(spaceId);
const actionToRun = {
...action,
params: injectActionParams({
- ruleId,
- spaceId,
actionTypeId,
+ ruleUrl,
actionParams: transformActionParams({
actionsPlugin,
alertId: ruleId,
@@ -286,7 +286,7 @@ export class ExecutionHandler<
alertParams: this.rule.params,
actionParams: action.params,
flapping: executableAlert.getFlapping(),
- ruleUrl: this.buildRuleUrl(spaceId),
+ ruleUrl,
}),
}),
};
diff --git a/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts b/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts
index 0416a3c4d1214..f34a5b0204357 100644
--- a/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts
+++ b/x-pack/plugins/alerting/server/task_runner/inject_action_params.test.ts
@@ -14,8 +14,6 @@ describe('injectActionParams', () => {
};
const result = injectActionParams({
actionParams,
- ruleId: '1',
- spaceId: 'the-space',
actionTypeId: '.server-log',
});
expect(result).toMatchInlineSnapshot(`
@@ -33,9 +31,8 @@ describe('injectActionParams', () => {
};
const result = injectActionParams({
actionParams,
- ruleId: '1',
- spaceId: 'default',
actionTypeId: '.email',
+ ruleUrl: 'http://localhost:5601/app/management/insightsAndAlerting/triggersActions/rule/1',
});
expect(result).toMatchInlineSnapshot(`
Object {
@@ -50,7 +47,7 @@ describe('injectActionParams', () => {
`);
});
- test('injects viewInKibanaPath and viewInKibanaText when actionTypeId is .email and spaceId is undefined', () => {
+ test('injects viewInKibanaPath as empty string when the ruleUrl is undefined', () => {
const actionParams = {
body: {
message: 'State: "{{state.value}}", Context: "{{context.value}}"',
@@ -58,8 +55,6 @@ describe('injectActionParams', () => {
};
const result = injectActionParams({
actionParams,
- ruleId: '1',
- spaceId: undefined,
actionTypeId: '.email',
});
expect(result).toMatchInlineSnapshot(`
@@ -68,32 +63,7 @@ describe('injectActionParams', () => {
"message": "State: \\"{{state.value}}\\", Context: \\"{{context.value}}\\"",
},
"kibanaFooterLink": Object {
- "path": "/app/management/insightsAndAlerting/triggersActions/rule/1",
- "text": "View rule in Kibana",
- },
- }
- `);
- });
-
- test('injects viewInKibanaPath with space ID and viewInKibanaText when actionTypeId is .email', () => {
- const actionParams = {
- body: {
- message: 'State: "{{state.value}}", Context: "{{context.value}}"',
- },
- };
- const result = injectActionParams({
- actionParams,
- ruleId: '1',
- spaceId: 'not-the-default',
- actionTypeId: '.email',
- });
- expect(result).toMatchInlineSnapshot(`
- Object {
- "body": Object {
- "message": "State: \\"{{state.value}}\\", Context: \\"{{context.value}}\\"",
- },
- "kibanaFooterLink": Object {
- "path": "/s/not-the-default/app/management/insightsAndAlerting/triggersActions/rule/1",
+ "path": "",
"text": "View rule in Kibana",
},
}
diff --git a/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts b/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts
index f2a41038e257e..e6f7e889a926c 100644
--- a/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts
+++ b/x-pack/plugins/alerting/server/task_runner/inject_action_params.ts
@@ -9,27 +9,29 @@ import { i18n } from '@kbn/i18n';
import { RuleActionParams } from '../types';
export interface InjectActionParamsOpts {
- ruleId: string;
- spaceId: string | undefined;
actionTypeId: string;
actionParams: RuleActionParams;
+ ruleUrl?: string;
}
export function injectActionParams({
- ruleId,
- spaceId,
actionTypeId,
actionParams,
+ ruleUrl = '',
}: InjectActionParamsOpts) {
// Inject kibanaFooterLink if action type is email. This is used by the email action type
// to inject a "View alert in Kibana" with a URL in the email's footer.
if (actionTypeId === '.email') {
- const spacePrefix =
- spaceId && spaceId.length > 0 && spaceId !== 'default' ? `/s/${spaceId}` : '';
+ let path;
+ try {
+ path = new URL(ruleUrl).pathname;
+ } catch (e) {
+ path = '';
+ }
return {
...actionParams,
kibanaFooterLink: {
- path: `${spacePrefix}/app/management/insightsAndAlerting/triggersActions/rule/${ruleId}`,
+ path,
text: i18n.translate('xpack.alerting.injectActionParams.email.kibanaFooterLinkText', {
defaultMessage: 'View rule in Kibana',
}),
diff --git a/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx b/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx
index 2237e91c74922..9fcb9c10e148f 100644
--- a/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx
+++ b/x-pack/plugins/apm/public/components/app/diagnostics/apm_documents_tab.tsx
@@ -11,7 +11,7 @@ import {
EuiBasicTableColumn,
EuiSpacer,
} from '@elastic/eui';
-import React, { useState } from 'react';
+import React, { useState, useMemo } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { orderBy } from 'lodash';
import { useApmParams } from '../../../hooks/use_apm_params';
@@ -22,6 +22,14 @@ import { useDiagnosticsContext } from './context/use_diagnostics';
import { ApmPluginStartDeps } from '../../../plugin';
import { SearchBar } from '../../shared/search_bar/search_bar';
+function formatDocCount(count?: number) {
+ if (count === undefined) {
+ return '-';
+ }
+
+ return asInteger(count);
+}
+
export function DiagnosticsApmDocuments() {
const { diagnosticsBundle, isImported } = useDiagnosticsContext();
const { discover } = useKibana().services;
@@ -31,7 +39,20 @@ export function DiagnosticsApmDocuments() {
query: { rangeFrom, rangeTo },
} = useApmParams('/diagnostics/documents');
- const items = diagnosticsBundle?.apmEvents ?? [];
+ const items = useMemo(() => {
+ return (
+ diagnosticsBundle?.apmEvents.filter(({ legacy, docCount, intervals }) => {
+ const isLegacyAndUnused =
+ legacy === true &&
+ docCount === 0 &&
+ intervals &&
+ Object.values(intervals).every((interval) => interval === 0);
+
+ return !isLegacyAndUnused;
+ }) ?? []
+ );
+ }, [diagnosticsBundle?.apmEvents]);
+
const columns: Array> = [
{
name: 'Name',
@@ -48,24 +69,24 @@ export function DiagnosticsApmDocuments() {
name: '1m',
field: 'intervals.1m',
render: (_, { intervals }) => {
- const interval = intervals?.['1m'];
- return interval ? asInteger(interval) : '-';
+ const docCount = intervals?.['1m'];
+ return formatDocCount(docCount);
},
},
{
name: '10m',
field: 'intervals.10m',
render: (_, { intervals }) => {
- const interval = intervals?.['10m'];
- return interval ? asInteger(interval) : '-';
+ const docCount = intervals?.['10m'];
+ return formatDocCount(docCount);
},
},
{
name: '60m',
field: 'intervals.60m',
render: (_, { intervals }) => {
- const interval = intervals?.['60m'];
- return interval ? asInteger(interval) : '-';
+ const docCount = intervals?.['60m'];
+ return formatDocCount(docCount);
},
},
{
diff --git a/x-pack/plugins/apm/public/components/app/diagnostics/context/diagnostics_context.tsx b/x-pack/plugins/apm/public/components/app/diagnostics/context/diagnostics_context.tsx
index 2292071d68642..488c3bc4dfbe0 100644
--- a/x-pack/plugins/apm/public/components/app/diagnostics/context/diagnostics_context.tsx
+++ b/x-pack/plugins/apm/public/components/app/diagnostics/context/diagnostics_context.tsx
@@ -39,6 +39,7 @@ export function DiagnosticsContextProvider({
const { data, status, refetch } = useFetcher(
(callApmApi) => {
return callApmApi(`GET /internal/apm/diagnostics`, {
+ isCachable: false,
params: {
query: {
start,
diff --git a/x-pack/plugins/apm/public/components/app/diagnostics/index_pattern_settings_tab.tsx b/x-pack/plugins/apm/public/components/app/diagnostics/index_pattern_settings_tab.tsx
index 38445b8d6ebbf..8d97a04316851 100644
--- a/x-pack/plugins/apm/public/components/app/diagnostics/index_pattern_settings_tab.tsx
+++ b/x-pack/plugins/apm/public/components/app/diagnostics/index_pattern_settings_tab.tsx
@@ -36,7 +36,7 @@ export function DiagnosticsIndexPatternSettings() {
!indexTemplatesByIndexPattern ||
indexTemplatesByIndexPattern?.length === 0
) {
- return null;
+ return No settings to display;
}
const elms = indexTemplatesByIndexPattern.map(
diff --git a/x-pack/plugins/apm/public/components/app/diagnostics/summary_tab/index.tsx b/x-pack/plugins/apm/public/components/app/diagnostics/summary_tab/index.tsx
index 4ff7ffd1c9f71..d4aeadd9223a7 100644
--- a/x-pack/plugins/apm/public/components/app/diagnostics/summary_tab/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/diagnostics/summary_tab/index.tsx
@@ -6,13 +6,30 @@
*/
import React from 'react';
-import { EuiFlexGroup } from '@elastic/eui';
+import { EuiFlexGroup, EuiCallOut } from '@elastic/eui';
import { ApmIntegrationPackageStatus } from './apm_integration_package_status';
import { IndexTemplatesStatus } from './index_templates_status';
import { FieldMappingStatus } from './indicies_status';
import { DataStreamsStatus } from './data_streams_status';
+import { useDiagnosticsContext } from '../context/use_diagnostics';
export function DiagnosticsSummary() {
+ const { diagnosticsBundle } = useDiagnosticsContext();
+
+ const isCrossCluster = Object.values(
+ diagnosticsBundle?.apmIndices ?? {}
+ ).some((indicies) => indicies.includes(':'));
+
+ if (isCrossCluster) {
+ return (
+
+ The APM index settings is targetting remote clusters. Please note: this
+ is not currently supported by the Diagnostics Tool and functionality
+ will therefore be limited.
+
+ );
+ }
+
return (
diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/index.tsx
index 318457537f5d9..6e70edf641b8a 100644
--- a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/index.tsx
@@ -163,7 +163,7 @@ export function AgentExplorer() {
{i18n.translate('xpack.apm.settings.agentExplorer.descriptionText', {
defaultMessage:
- 'Agent Explorer Technical Preview provides an inventory and details of deployed Agents.',
+ 'Agent Explorer provides an inventory and details of deployed Agents.',
})}
diff --git a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_apm_events.ts b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_apm_events.ts
index 42de61050abd9..5c700d1b24945 100644
--- a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_apm_events.ts
+++ b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_apm_events.ts
@@ -7,6 +7,7 @@
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import { kqlQuery, rangeQuery } from '@kbn/observability-plugin/server';
+import { merge } from 'lodash';
import {
PROCESSOR_EVENT,
METRICSET_NAME,
@@ -18,6 +19,7 @@ import { getTypedSearch, TypedSearch } from '../create_typed_es_client';
import { getApmIndexPatterns } from './get_indices';
export interface ApmEvent {
+ legacy?: boolean;
name: string;
kuery: string;
index: string[];
@@ -53,7 +55,7 @@ export async function getApmEvents({
}),
getEventWithMetricsetInterval({
...commonProps,
- name: 'Metric: Service transaction (with summary field)',
+ name: 'Metric: Service transaction (8.7+)',
index: getApmIndexPatterns([apmIndices.metric]),
kuery: mergeKueries(
`${PROCESSOR_EVENT}: "metric" AND ${METRICSET_NAME}: "service_transaction" AND ${TRANSACTION_DURATION_SUMMARY} :* `,
@@ -62,7 +64,7 @@ export async function getApmEvents({
}),
getEventWithMetricsetInterval({
...commonProps,
- name: 'Metric: Transaction (with summary field)',
+ name: 'Metric: Transaction (8.7+)',
index: getApmIndexPatterns([apmIndices.metric]),
kuery: mergeKueries(
`${PROCESSOR_EVENT}: "metric" AND ${METRICSET_NAME}: "transaction" AND ${TRANSACTION_DURATION_SUMMARY} :* `,
@@ -71,7 +73,8 @@ export async function getApmEvents({
}),
getEventWithMetricsetInterval({
...commonProps,
- name: 'Metric: Service transaction (without summary field)',
+ legacy: true,
+ name: 'Metric: Service transaction (pre-8.7)',
index: getApmIndexPatterns([apmIndices.metric]),
kuery: mergeKueries(
`${PROCESSOR_EVENT}: "metric" AND ${METRICSET_NAME}: "service_transaction" AND not ${TRANSACTION_DURATION_SUMMARY} :* `,
@@ -80,7 +83,8 @@ export async function getApmEvents({
}),
getEventWithMetricsetInterval({
...commonProps,
- name: 'Metric: Transaction (without summary field)',
+ legacy: true,
+ name: 'Metric: Transaction (pre-8.7)',
index: getApmIndexPatterns([apmIndices.metric]),
kuery: mergeKueries(
`${PROCESSOR_EVENT}: "metric" AND ${METRICSET_NAME}: "transaction" AND not ${TRANSACTION_DURATION_SUMMARY} :* `,
@@ -129,6 +133,7 @@ export async function getApmEvents({
}
async function getEventWithMetricsetInterval({
+ legacy,
name,
index,
start,
@@ -136,6 +141,7 @@ async function getEventWithMetricsetInterval({
kuery,
typedSearch,
}: {
+ legacy?: boolean;
name: string;
index: string[];
start: number;
@@ -163,17 +169,23 @@ async function getEventWithMetricsetInterval({
},
});
+ const defaultIntervals = { '1m': 0, '10m': 0, '60m': 0 };
+ const foundIntervals = res.aggregations?.metricset_intervals.buckets.reduce<
+ Record
+ >((acc, item) => {
+ acc[item.key] = item.doc_count;
+ return acc;
+ }, {});
+
+ const intervals = merge(defaultIntervals, foundIntervals);
+
return {
+ legacy,
name,
kuery,
index,
docCount: res.hits.total.value,
- intervals: res.aggregations?.metricset_intervals.buckets.reduce<
- Record
- >((acc, item) => {
- acc[item.key] = item.doc_count;
- return acc;
- }, {}),
+ intervals,
};
}
diff --git a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_existing_index_templates.ts b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_existing_index_templates.ts
index 1c8f9ca9c652b..b980907acec78 100644
--- a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_existing_index_templates.ts
+++ b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_existing_index_templates.ts
@@ -6,6 +6,7 @@
*/
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
+import { getApmIndexTemplateNames } from '../helpers/get_apm_index_template_names';
import { getIndexTemplate } from './get_index_template';
export type ApmIndexTemplateStates = Record<
@@ -16,11 +17,10 @@ export type ApmIndexTemplateStates = Record<
// Check whether the default APM index templates exist
export async function getExistingApmIndexTemplates({
esClient,
- apmIndexTemplateNames,
}: {
esClient: ElasticsearchClient;
- apmIndexTemplateNames: string[];
}) {
+ const apmIndexTemplateNames = getApmIndexTemplateNames();
const values = await Promise.all(
apmIndexTemplateNames.map(async (indexTemplateName) => {
const res = await getIndexTemplate(esClient, { name: indexTemplateName });
diff --git a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_index_templates_by_index_pattern.ts b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_index_templates_by_index_pattern.ts
index 2d57e773d5856..7e5b4220e9182 100644
--- a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_index_templates_by_index_pattern.ts
+++ b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_index_templates_by_index_pattern.ts
@@ -11,7 +11,7 @@ import { orderBy } from 'lodash';
import { ApmIndicesConfig } from '../../settings/apm_indices/get_apm_indices';
import { getApmIndexPatterns } from './get_indices';
import { getIndexTemplate } from './get_index_template';
-import { getApmIndexTemplateNames } from '../get_apm_index_template_names';
+import { getApmIndexTemplateNames } from '../helpers/get_apm_index_template_names';
export async function getIndexTemplatesByIndexPattern({
esClient,
@@ -27,11 +27,16 @@ export async function getIndexTemplatesByIndexPattern({
apmIndices.transaction,
]);
- return Promise.all(
- indexPatterns.map(async (indexPattern) =>
- getSimulatedIndexTemplateForIndexPattern({ indexPattern, esClient })
- )
- );
+ try {
+ return await Promise.all(
+ indexPatterns.map(async (indexPattern) =>
+ getSimulatedIndexTemplateForIndexPattern({ indexPattern, esClient })
+ )
+ );
+ } catch (e) {
+ console.error(e);
+ return [];
+ }
}
async function getSimulatedIndexTemplateForIndexPattern({
diff --git a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_indices_states.ts b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_indices_states.ts
index 1a2622dfb95d7..e348074360736 100644
--- a/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_indices_states.ts
+++ b/x-pack/plugins/apm/server/routes/diagnostics/bundle/get_indices_states.ts
@@ -11,7 +11,7 @@ import {
IngestGetPipelineResponse,
} from '@elastic/elasticsearch/lib/api/types';
import { SERVICE_NAME } from '../../../../common/es_fields/apm';
-import { getApmIndexTemplateNames } from '../get_apm_index_template_names';
+import { getApmIndexTemplateNames } from '../helpers/get_apm_index_template_names';
export function getIndicesStates({
indices,
diff --git a/x-pack/plugins/apm/server/routes/diagnostics/get_apm_index_template_names.ts b/x-pack/plugins/apm/server/routes/diagnostics/get_apm_index_template_names.ts
deleted file mode 100644
index f480fcbe20071..0000000000000
--- a/x-pack/plugins/apm/server/routes/diagnostics/get_apm_index_template_names.ts
+++ /dev/null
@@ -1,28 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-export function getApmIndexTemplateNames() {
- const indexTemplateNames = [
- 'logs-apm.app',
- 'logs-apm.error',
- 'metrics-apm.app',
- 'metrics-apm.internal',
- 'traces-apm.rum',
- 'traces-apm.sampled',
- 'traces-apm',
- ];
-
- const rollupIndexTemplateNames = ['1m', '10m', '60m'].flatMap((interval) => {
- return [
- 'metrics-apm.service_destination',
- 'metrics-apm.service_summary',
- 'metrics-apm.service_transaction',
- 'metrics-apm.transaction',
- ].map((ds) => `${ds}.${interval}`);
- });
-
- return [...indexTemplateNames, ...rollupIndexTemplateNames];
-}
diff --git a/x-pack/plugins/apm/server/routes/diagnostics/get_diagnostics_bundle.ts b/x-pack/plugins/apm/server/routes/diagnostics/get_diagnostics_bundle.ts
index dd2fb3559c0f7..51a71167c4ec7 100644
--- a/x-pack/plugins/apm/server/routes/diagnostics/get_diagnostics_bundle.ts
+++ b/x-pack/plugins/apm/server/routes/diagnostics/get_diagnostics_bundle.ts
@@ -5,12 +5,10 @@
* 2.0.
*/
-import { IndicesGetIndexTemplateIndexTemplateItem } from '@elastic/elasticsearch/lib/api/types';
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import { ApmIndicesConfig } from '../settings/apm_indices/get_apm_indices';
import { getDataStreams } from './bundle/get_data_streams';
import { getNonDataStreamIndices } from './bundle/get_non_data_stream_indices';
-import { getApmIndexTemplateNames } from './get_apm_index_template_names';
import { getElasticsearchVersion } from './get_elasticsearch_version';
import { getIndexTemplatesByIndexPattern } from './bundle/get_index_templates_by_index_pattern';
import { getExistingApmIndexTemplates } from './bundle/get_existing_index_templates';
@@ -18,6 +16,7 @@ import { getFieldCaps } from './bundle/get_field_caps';
import { getIndicesAndIngestPipelines } from './bundle/get_indices';
import { getIndicesStates } from './bundle/get_indices_states';
import { getApmEvents } from './bundle/get_apm_events';
+import { getApmIndexTemplates } from './helpers/get_apm_index_template_names';
const DEFEAULT_START = Date.now() - 60 * 5 * 1000; // 5 minutes
const DEFAULT_END = Date.now();
@@ -35,8 +34,6 @@ export async function getDiagnosticsBundle({
end: number | undefined;
kuery: string | undefined;
}) {
- const apmIndexTemplateNames = getApmIndexTemplateNames();
-
const { indices, ingestPipelines } = await getIndicesAndIngestPipelines({
esClient,
apmIndices,
@@ -49,7 +46,6 @@ export async function getDiagnosticsBundle({
const existingIndexTemplates = await getExistingApmIndexTemplates({
esClient,
- apmIndexTemplateNames,
});
const fieldCaps = await getFieldCaps({ esClient, apmIndices });
@@ -75,6 +71,7 @@ export async function getDiagnosticsBundle({
return {
created_at: new Date().toISOString(),
+ apmIndices,
elasticsearchVersion: await getElasticsearchVersion(esClient),
esResponses: {
fieldCaps,
@@ -82,10 +79,7 @@ export async function getDiagnosticsBundle({
ingestPipelines,
existingIndexTemplates,
},
- apmIndexTemplates: getApmIndexTemplates(
- apmIndexTemplateNames,
- existingIndexTemplates
- ),
+ apmIndexTemplates: getApmIndexTemplates(existingIndexTemplates),
invalidIndices,
validIndices,
indexTemplatesByIndexPattern,
@@ -95,35 +89,3 @@ export async function getDiagnosticsBundle({
params: { start, end, kuery },
};
}
-
-function getApmIndexTemplates(
- apmIndexTemplateNames: string[],
- existingIndexTemplates: IndicesGetIndexTemplateIndexTemplateItem[]
-) {
- const standardIndexTemplates = apmIndexTemplateNames.map((templateName) => {
- const matchingTemplate = existingIndexTemplates.find(
- ({ name }) => name === templateName
- );
-
- return {
- name: templateName,
- exists: Boolean(matchingTemplate),
- isNonStandard: false,
- };
- });
-
- const nonStandardIndexTemplates = existingIndexTemplates
- .filter(
- (indexTemplate) =>
- standardIndexTemplates.some(
- ({ name }) => name === indexTemplate.name
- ) === false
- )
- .map((indexTemplate) => ({
- name: indexTemplate.name,
- isNonStandard: true,
- exists: true,
- }));
-
- return [...standardIndexTemplates, ...nonStandardIndexTemplates];
-}
diff --git a/x-pack/plugins/apm/server/routes/diagnostics/helpers/get_apm_index_template_names.ts b/x-pack/plugins/apm/server/routes/diagnostics/helpers/get_apm_index_template_names.ts
new file mode 100644
index 0000000000000..7018ba20edb4b
--- /dev/null
+++ b/x-pack/plugins/apm/server/routes/diagnostics/helpers/get_apm_index_template_names.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { IndicesGetIndexTemplateIndexTemplateItem } from '@elastic/elasticsearch/lib/api/types';
+
+export function getApmIndexTemplateNames() {
+ const indexTemplateNames = [
+ 'logs-apm.app',
+ 'logs-apm.error',
+ 'metrics-apm.app',
+ 'metrics-apm.internal',
+ 'traces-apm.rum',
+ 'traces-apm.sampled',
+ 'traces-apm',
+ ];
+
+ const rollupIndexTemplateNames = ['1m', '10m', '60m'].flatMap((interval) => {
+ return [
+ 'metrics-apm.service_destination',
+ 'metrics-apm.service_summary',
+ 'metrics-apm.service_transaction',
+ 'metrics-apm.transaction',
+ ].map((ds) => `${ds}.${interval}`);
+ });
+
+ return [...indexTemplateNames, ...rollupIndexTemplateNames];
+}
+
+export function getApmIndexTemplates(
+ existingIndexTemplates: IndicesGetIndexTemplateIndexTemplateItem[]
+) {
+ const apmIndexTemplateNames = getApmIndexTemplateNames();
+ const standardIndexTemplates = apmIndexTemplateNames.map((templateName) => {
+ const matchingTemplate = existingIndexTemplates.find(
+ ({ name }) => name === templateName
+ );
+
+ return {
+ name: templateName,
+ exists: Boolean(matchingTemplate),
+ isNonStandard: false,
+ };
+ });
+
+ const nonStandardIndexTemplates = existingIndexTemplates
+ .filter(
+ (indexTemplate) =>
+ standardIndexTemplates.some(
+ ({ name }) => name === indexTemplate.name
+ ) === false
+ )
+ .map((indexTemplate) => ({
+ name: indexTemplate.name,
+ isNonStandard: true,
+ exists: true,
+ }));
+
+ return [...standardIndexTemplates, ...nonStandardIndexTemplates];
+}
diff --git a/x-pack/plugins/apm/server/routes/diagnostics/route.ts b/x-pack/plugins/apm/server/routes/diagnostics/route.ts
index c25a67581482e..f4698a913db58 100644
--- a/x-pack/plugins/apm/server/routes/diagnostics/route.ts
+++ b/x-pack/plugins/apm/server/routes/diagnostics/route.ts
@@ -15,7 +15,10 @@ import {
import * as t from 'io-ts';
import { isoToEpochRt } from '@kbn/io-ts-utils';
import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
-import { getApmIndices } from '../settings/apm_indices/get_apm_indices';
+import {
+ ApmIndicesConfig,
+ getApmIndices,
+} from '../settings/apm_indices/get_apm_indices';
import { ApmEvent } from './bundle/get_apm_events';
import { getDiagnosticsBundle } from './get_diagnostics_bundle';
import { getFleetPackageInfo } from './get_fleet_package_info';
@@ -53,6 +56,7 @@ const getDiagnosticsRoute = createApmServerRoute({
indices: IndicesGetResponse;
ingestPipelines: IngestGetPipelineResponse;
};
+ apmIndices: ApmIndicesConfig;
apmIndexTemplates: Array<{
name: string;
isNonStandard: boolean;
diff --git a/x-pack/plugins/cases/common/api/cases/index.ts b/x-pack/plugins/cases/common/api/cases/index.ts
index 2b561a1dff124..c45893d6affcc 100644
--- a/x-pack/plugins/cases/common/api/cases/index.ts
+++ b/x-pack/plugins/cases/common/api/cases/index.ts
@@ -6,7 +6,6 @@
*/
export * from './case';
-export * from './configure';
export * from './comment';
export * from './status';
export * from './user_actions';
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/dashboard/route.ts b/x-pack/plugins/cases/common/types/api/configure/latest.ts
similarity index 82%
rename from x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/dashboard/route.ts
rename to x-pack/plugins/cases/common/types/api/configure/latest.ts
index 33821fcd90d26..25300c97a6d2e 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/dashboard/route.ts
+++ b/x-pack/plugins/cases/common/types/api/configure/latest.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export function getRulesDashboardDataRoute(): void {}
+export * from './v1';
diff --git a/x-pack/plugins/cases/common/api/cases/configure.test.ts b/x-pack/plugins/cases/common/types/api/configure/v1.test.ts
similarity index 53%
rename from x-pack/plugins/cases/common/api/cases/configure.test.ts
rename to x-pack/plugins/cases/common/types/api/configure/v1.test.ts
index f1b462bd97ace..213706c73cceb 100644
--- a/x-pack/plugins/cases/common/api/cases/configure.test.ts
+++ b/x-pack/plugins/cases/common/types/api/configure/v1.test.ts
@@ -5,15 +5,13 @@
* 2.0.
*/
-import { ConnectorTypes } from '../connectors';
+import { ConnectorTypes } from '../../../api';
import {
- ConfigurationAttributesRt,
CaseConfigureRequestParamsRt,
- ConfigurationRt,
ConfigurationPatchRequestRt,
ConfigurationRequestRt,
GetConfigurationFindRequestRt,
-} from './configure';
+} from './v1';
describe('configure', () => {
const serviceNow = {
@@ -23,13 +21,6 @@ describe('configure', () => {
fields: null,
};
- const resilient = {
- id: 'resilient-2',
- name: 'Resilient',
- type: ConnectorTypes.resilient,
- fields: null,
- };
-
describe('ConfigurationRequestRt', () => {
const defaultRequest = {
connector: serviceNow,
@@ -82,100 +73,6 @@ describe('configure', () => {
});
});
- describe('ConfigurationAttributesRt', () => {
- const defaultRequest = {
- connector: resilient,
- closure_type: 'close-by-user',
- owner: 'cases',
- created_at: '2020-02-19T23:06:33.798Z',
- created_by: {
- full_name: 'Leslie Knope',
- username: 'lknope',
- email: 'leslie.knope@elastic.co',
- },
- updated_at: '2020-02-19T23:06:33.798Z',
- updated_by: {
- full_name: 'Leslie Knope',
- username: 'lknope',
- email: 'leslie.knope@elastic.co',
- },
- };
-
- it('has expected attributes in request', () => {
- const query = ConfigurationAttributesRt.decode(defaultRequest);
-
- expect(query).toStrictEqual({
- _tag: 'Right',
- right: defaultRequest,
- });
- });
-
- it('removes foo:bar attributes from request', () => {
- const query = ConfigurationAttributesRt.decode({ ...defaultRequest, foo: 'bar' });
-
- expect(query).toStrictEqual({
- _tag: 'Right',
- right: defaultRequest,
- });
- });
- });
-
- describe('ConfigurationRt', () => {
- const defaultRequest = {
- connector: serviceNow,
- closure_type: 'close-by-user',
- created_at: '2020-02-19T23:06:33.798Z',
- created_by: {
- full_name: 'Leslie Knope',
- username: 'lknope',
- email: 'leslie.knope@elastic.co',
- },
- updated_at: '2020-02-19T23:06:33.798Z',
- updated_by: null,
- mappings: [
- {
- source: 'description',
- target: 'description',
- action_type: 'overwrite',
- },
- ],
- owner: 'cases',
- version: 'WzQ3LDFd',
- id: 'case-id',
- error: null,
- };
-
- it('has expected attributes in request', () => {
- const query = ConfigurationRt.decode(defaultRequest);
-
- expect(query).toStrictEqual({
- _tag: 'Right',
- right: defaultRequest,
- });
- });
-
- it('removes foo:bar attributes from request', () => {
- const query = ConfigurationRt.decode({ ...defaultRequest, foo: 'bar' });
-
- expect(query).toStrictEqual({
- _tag: 'Right',
- right: defaultRequest,
- });
- });
-
- it('removes foo:bar attributes from mappings', () => {
- const query = ConfigurationRt.decode({
- ...defaultRequest,
- mappings: [{ ...defaultRequest.mappings[0], foo: 'bar' }],
- });
-
- expect(query).toStrictEqual({
- _tag: 'Right',
- right: defaultRequest,
- });
- });
- });
-
describe('GetConfigurationFindRequestRt', () => {
const defaultRequest = {
owner: ['cases'],
diff --git a/x-pack/plugins/cases/common/types/api/configure/v1.ts b/x-pack/plugins/cases/common/types/api/configure/v1.ts
new file mode 100644
index 0000000000000..fffff5310f6af
--- /dev/null
+++ b/x-pack/plugins/cases/common/types/api/configure/v1.ts
@@ -0,0 +1,38 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import * as rt from 'io-ts';
+import type { Configurations, Configuration } from '../../domain/configure/v1';
+import { ConfigurationBasicWithoutOwnerRt, CasesConfigureBasicRt } from '../../domain/configure/v1';
+
+export const ConfigurationRequestRt = CasesConfigureBasicRt;
+
+export const GetConfigurationFindRequestRt = rt.exact(
+ rt.partial({
+ /**
+ * The configuration plugin owner to filter the search by. If this is left empty the results will include all configurations
+ * that the user has permissions to access
+ */
+ owner: rt.union([rt.array(rt.string), rt.string]),
+ })
+);
+
+export const CaseConfigureRequestParamsRt = rt.strict({
+ configuration_id: rt.string,
+});
+
+export const ConfigurationPatchRequestRt = rt.intersection([
+ rt.exact(rt.partial(ConfigurationBasicWithoutOwnerRt.type.props)),
+ rt.strict({ version: rt.string }),
+]);
+
+export type ConfigurationRequest = rt.TypeOf;
+export type ConfigurationPatchRequest = rt.TypeOf;
+export type GetConfigurationFindRequest = rt.TypeOf;
+export type GetConfigureResponse = Configurations;
+export type CreateConfigureResponse = Configuration;
+export type UpdateConfigureResponse = Configuration;
diff --git a/x-pack/plugins/cases/common/types/api/index.ts b/x-pack/plugins/cases/common/types/api/index.ts
new file mode 100644
index 0000000000000..7a9b19ba2250d
--- /dev/null
+++ b/x-pack/plugins/cases/common/types/api/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+// Latest
+export * from './configure/latest';
+
+// V1
+export * as configureApiV1 from './configure/v1';
diff --git a/x-pack/plugins/cases/common/types/domain/configure/latest.ts b/x-pack/plugins/cases/common/types/domain/configure/latest.ts
new file mode 100644
index 0000000000000..25300c97a6d2e
--- /dev/null
+++ b/x-pack/plugins/cases/common/types/domain/configure/latest.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export * from './v1';
diff --git a/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts b/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts
new file mode 100644
index 0000000000000..f27db54b784f5
--- /dev/null
+++ b/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts
@@ -0,0 +1,119 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { ConnectorTypes } from '../../../api';
+import { ConfigurationAttributesRt, ConfigurationRt } from './v1';
+
+describe('configure', () => {
+ const serviceNow = {
+ id: 'servicenow-1',
+ name: 'SN 1',
+ type: ConnectorTypes.serviceNowITSM,
+ fields: null,
+ };
+
+ const resilient = {
+ id: 'resilient-2',
+ name: 'Resilient',
+ type: ConnectorTypes.resilient,
+ fields: null,
+ };
+
+ describe('ConfigurationAttributesRt', () => {
+ const defaultRequest = {
+ connector: resilient,
+ closure_type: 'close-by-user',
+ owner: 'cases',
+ created_at: '2020-02-19T23:06:33.798Z',
+ created_by: {
+ full_name: 'Leslie Knope',
+ username: 'lknope',
+ email: 'leslie.knope@elastic.co',
+ },
+ updated_at: '2020-02-19T23:06:33.798Z',
+ updated_by: {
+ full_name: 'Leslie Knope',
+ username: 'lknope',
+ email: 'leslie.knope@elastic.co',
+ },
+ };
+
+ it('has expected attributes in request', () => {
+ const query = ConfigurationAttributesRt.decode(defaultRequest);
+
+ expect(query).toStrictEqual({
+ _tag: 'Right',
+ right: defaultRequest,
+ });
+ });
+
+ it('removes foo:bar attributes from request', () => {
+ const query = ConfigurationAttributesRt.decode({ ...defaultRequest, foo: 'bar' });
+
+ expect(query).toStrictEqual({
+ _tag: 'Right',
+ right: defaultRequest,
+ });
+ });
+ });
+
+ describe('ConfigurationRt', () => {
+ const defaultRequest = {
+ connector: serviceNow,
+ closure_type: 'close-by-user',
+ created_at: '2020-02-19T23:06:33.798Z',
+ created_by: {
+ full_name: 'Leslie Knope',
+ username: 'lknope',
+ email: 'leslie.knope@elastic.co',
+ },
+ updated_at: '2020-02-19T23:06:33.798Z',
+ updated_by: null,
+ mappings: [
+ {
+ source: 'description',
+ target: 'description',
+ action_type: 'overwrite',
+ },
+ ],
+ owner: 'cases',
+ version: 'WzQ3LDFd',
+ id: 'case-id',
+ error: null,
+ };
+
+ it('has expected attributes in request', () => {
+ const query = ConfigurationRt.decode(defaultRequest);
+
+ expect(query).toStrictEqual({
+ _tag: 'Right',
+ right: defaultRequest,
+ });
+ });
+
+ it('removes foo:bar attributes from request', () => {
+ const query = ConfigurationRt.decode({ ...defaultRequest, foo: 'bar' });
+
+ expect(query).toStrictEqual({
+ _tag: 'Right',
+ right: defaultRequest,
+ });
+ });
+
+ it('removes foo:bar attributes from mappings', () => {
+ const query = ConfigurationRt.decode({
+ ...defaultRequest,
+ mappings: [{ ...defaultRequest.mappings[0], foo: 'bar' }],
+ });
+
+ expect(query).toStrictEqual({
+ _tag: 'Right',
+ right: defaultRequest,
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/cases/common/api/cases/configure.ts b/x-pack/plugins/cases/common/types/domain/configure/v1.ts
similarity index 60%
rename from x-pack/plugins/cases/common/api/cases/configure.ts
rename to x-pack/plugins/cases/common/types/domain/configure/v1.ts
index 9a24241c424ab..080d971026afc 100644
--- a/x-pack/plugins/cases/common/api/cases/configure.ts
+++ b/x-pack/plugins/cases/common/types/domain/configure/v1.ts
@@ -6,9 +6,7 @@
*/
import * as rt from 'io-ts';
-import { CaseConnectorRt } from '../connectors/connector';
-import { ConnectorMappingsRt } from '../connectors/mappings';
-import { UserRt } from '../user';
+import { CaseConnectorRt, UserRt, ConnectorMappingsRt } from '../../../api';
const ClosureTypeRt = rt.union([rt.literal('close-by-user'), rt.literal('close-by-pushing')]);
@@ -23,7 +21,7 @@ export const ConfigurationBasicWithoutOwnerRt = rt.strict({
closure_type: ClosureTypeRt,
});
-const CasesConfigureBasicRt = rt.intersection([
+export const CasesConfigureBasicRt = rt.intersection([
ConfigurationBasicWithoutOwnerRt,
rt.strict({
/**
@@ -33,12 +31,6 @@ const CasesConfigureBasicRt = rt.intersection([
}),
]);
-export const ConfigurationRequestRt = CasesConfigureBasicRt;
-export const ConfigurationPatchRequestRt = rt.intersection([
- rt.exact(rt.partial(ConfigurationBasicWithoutOwnerRt.type.props)),
- rt.strict({ version: rt.string }),
-]);
-
export const ConfigurationActivityFieldsRt = rt.strict({
created_at: rt.string,
created_by: UserRt,
@@ -62,27 +54,9 @@ export const ConfigurationRt = rt.intersection([
}),
]);
-export const GetConfigurationFindRequestRt = rt.exact(
- rt.partial({
- /**
- * The configuration plugin owner to filter the search by. If this is left empty the results will include all configurations
- * that the user has permissions to access
- */
- owner: rt.union([rt.array(rt.string), rt.string]),
- })
-);
-
-export const CaseConfigureRequestParamsRt = rt.strict({
- configuration_id: rt.string,
-});
-
export const ConfigurationsRt = rt.array(ConfigurationRt);
export type ClosureType = rt.TypeOf;
-export type ConfigurationRequest = rt.TypeOf;
-export type ConfigurationPatchRequest = rt.TypeOf;
export type ConfigurationAttributes = rt.TypeOf;
export type Configuration = rt.TypeOf;
export type Configurations = rt.TypeOf;
-
-export type GetConfigurationFindRequest = rt.TypeOf;
diff --git a/x-pack/plugins/cases/common/types/domain/index.ts b/x-pack/plugins/cases/common/types/domain/index.ts
new file mode 100644
index 0000000000000..2c5169582c1da
--- /dev/null
+++ b/x-pack/plugins/cases/common/types/domain/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+// Latest
+export * from './configure/latest';
+
+// V1
+export * as configureDomainV1 from './configure/v1';
diff --git a/x-pack/plugins/cases/docs/openapi/bundled.json b/x-pack/plugins/cases/docs/openapi/bundled.json
index 2ccef20d760a1..2ce8031bc1cb9 100644
--- a/x-pack/plugins/cases/docs/openapi/bundled.json
+++ b/x-pack/plugins/cases/docs/openapi/bundled.json
@@ -25,6 +25,1814 @@
}
],
"paths": {
+ "/api/cases": {
+ "post": {
+ "summary": "Creates a case in the default space.",
+ "operationId": "createCaseDefaultSpace",
+ "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/create_case_request"
+ },
+ "examples": {
+ "createCaseRequest": {
+ "$ref": "#/components/examples/create_case_request"
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/case_response_properties"
+ },
+ "examples": {
+ "createCaseResponse": {
+ "$ref": "#/components/examples/create_case_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "delete": {
+ "summary": "Deletes one or more cases in the default space.",
+ "operationId": "deleteCaseDefaultSpace",
+ "description": "You must have `read` or `all` privileges and the `delete` sub-feature privilege for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ },
+ {
+ "$ref": "#/components/parameters/ids"
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Indicates a successful call."
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "patch": {
+ "summary": "Updates one or more cases in the default space.",
+ "operationId": "updateCaseDefaultSpace",
+ "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/update_case_request"
+ },
+ "examples": {
+ "updateCaseRequest": {
+ "$ref": "#/components/examples/update_case_request"
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/case_response_properties"
+ }
+ },
+ "examples": {
+ "updateCaseResponse": {
+ "$ref": "#/components/examples/update_case_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/_find": {
+ "get": {
+ "summary": "Retrieves a paginated subset of cases in the default space.",
+ "operationId": "findCasesDefaultSpace",
+ "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/assignees"
+ },
+ {
+ "$ref": "#/components/parameters/category"
+ },
+ {
+ "$ref": "#/components/parameters/defaultSearchOperator"
+ },
+ {
+ "$ref": "#/components/parameters/from"
+ },
+ {
+ "$ref": "#/components/parameters/owner"
+ },
+ {
+ "$ref": "#/components/parameters/page_index"
+ },
+ {
+ "$ref": "#/components/parameters/page_size"
+ },
+ {
+ "$ref": "#/components/parameters/reporters"
+ },
+ {
+ "$ref": "#/components/parameters/search"
+ },
+ {
+ "$ref": "#/components/parameters/searchFields"
+ },
+ {
+ "$ref": "#/components/parameters/severity"
+ },
+ {
+ "$ref": "#/components/parameters/sortField"
+ },
+ {
+ "$ref": "#/components/parameters/sort_order"
+ },
+ {
+ "$ref": "#/components/parameters/status"
+ },
+ {
+ "$ref": "#/components/parameters/tags"
+ },
+ {
+ "$ref": "#/components/parameters/to"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "cases": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/case_response_properties"
+ }
+ },
+ "count_closed_cases": {
+ "type": "integer"
+ },
+ "count_in_progress_cases": {
+ "type": "integer"
+ },
+ "count_open_cases": {
+ "type": "integer"
+ },
+ "page": {
+ "type": "integer"
+ },
+ "per_page": {
+ "type": "integer"
+ },
+ "total": {
+ "type": "integer"
+ }
+ }
+ },
+ "examples": {
+ "findCaseResponse": {
+ "$ref": "#/components/examples/find_case_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/alerts/{alertId}": {
+ "get": {
+ "summary": "Returns the cases associated with a specific alert in the default space.",
+ "operationId": "getCasesByAlertDefaultSpace",
+ "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n",
+ "x-technical-preview": true,
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/alert_id"
+ },
+ {
+ "$ref": "#/components/parameters/owner"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "The case identifier."
+ },
+ "title": {
+ "type": "string",
+ "description": "The case title."
+ }
+ }
+ },
+ "example": [
+ {
+ "id": "06116b80-e1c3-11ec-be9b-9b1838238ee6",
+ "title": "security_case"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601s"
+ }
+ ]
+ },
+ "/api/cases/configure": {
+ "get": {
+ "summary": "Retrieves external connection details, such as the closure type and default connector for cases in the default space.",
+ "operationId": "getCaseConfigurationDefaultSpace",
+ "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/owner"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "closure_type": {
+ "$ref": "#/components/schemas/closure_types"
+ },
+ "connector": {
+ "type": "object",
+ "properties": {
+ "fields": {
+ "description": "The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.",
+ "nullable": true,
+ "type": "object"
+ },
+ "id": {
+ "description": "The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.",
+ "type": "string",
+ "example": "none"
+ },
+ "name": {
+ "description": "The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.",
+ "type": "string",
+ "example": "none"
+ },
+ "type": {
+ "$ref": "#/components/schemas/connector_types"
+ }
+ }
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time",
+ "example": "2022-06-01T17:07:17.767Z"
+ },
+ "created_by": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "full_name": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "username": {
+ "type": "string",
+ "example": "elastic",
+ "nullable": true
+ },
+ "profile_uid": {
+ "type": "string",
+ "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0"
+ }
+ }
+ },
+ "error": {
+ "type": "string",
+ "nullable": true,
+ "example": null
+ },
+ "id": {
+ "type": "string",
+ "example": "4a97a440-e1cd-11ec-be9b-9b1838238ee6"
+ },
+ "mappings": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "action_type": {
+ "type": "string",
+ "example": "overwrite"
+ },
+ "source": {
+ "type": "string",
+ "example": "title"
+ },
+ "target": {
+ "type": "string",
+ "example": "summary"
+ }
+ }
+ }
+ },
+ "owner": {
+ "$ref": "#/components/schemas/owners"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "example": "2022-06-01T19:58:48.169Z"
+ },
+ "updated_by": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "full_name": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "username": {
+ "type": "string",
+ "example": "elastic",
+ "nullable": true
+ },
+ "profile_uid": {
+ "type": "string",
+ "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0"
+ }
+ },
+ "nullable": true
+ },
+ "version": {
+ "type": "string",
+ "example": "WzIwNzMsMV0="
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "post": {
+ "summary": "Sets external connection details, such as the closure type and default connector for cases in the default space.",
+ "operationId": "setCaseConfigurationDefaultSpace",
+ "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API. If you set a default connector, it is automatically selected when you create cases in Kibana. If you use the create case API, however, you must still specify all of the connector details.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/set_case_configuration_request"
+ },
+ "examples": {
+ "setCaseConfigRequest": {
+ "$ref": "#/components/examples/set_case_configuration_request"
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "closure_type": {
+ "$ref": "#/components/schemas/closure_types"
+ },
+ "connector": {
+ "type": "object",
+ "properties": {
+ "fields": {
+ "description": "The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.",
+ "nullable": true,
+ "type": "object"
+ },
+ "id": {
+ "description": "The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.",
+ "type": "string",
+ "example": "none"
+ },
+ "name": {
+ "description": "The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.",
+ "type": "string",
+ "example": "none"
+ },
+ "type": {
+ "$ref": "#/components/schemas/connector_types"
+ }
+ }
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time",
+ "example": "2022-06-01T17:07:17.767Z"
+ },
+ "created_by": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "full_name": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "username": {
+ "type": "string",
+ "example": "elastic",
+ "nullable": true
+ },
+ "profile_uid": {
+ "type": "string",
+ "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0"
+ }
+ }
+ },
+ "error": {
+ "type": "string",
+ "nullable": true,
+ "example": null
+ },
+ "id": {
+ "type": "string",
+ "example": "4a97a440-e1cd-11ec-be9b-9b1838238ee6"
+ },
+ "mappings": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "action_type": {
+ "type": "string",
+ "example": "overwrite"
+ },
+ "source": {
+ "type": "string",
+ "example": "title"
+ },
+ "target": {
+ "type": "string",
+ "example": "summary"
+ }
+ }
+ }
+ },
+ "owner": {
+ "$ref": "#/components/schemas/owners"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "example": "2022-06-01T19:58:48.169Z"
+ },
+ "updated_by": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "full_name": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "username": {
+ "type": "string",
+ "example": "elastic",
+ "nullable": true
+ },
+ "profile_uid": {
+ "type": "string",
+ "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0"
+ }
+ },
+ "nullable": true
+ },
+ "version": {
+ "type": "string",
+ "example": "WzIwNzMsMV0="
+ }
+ }
+ },
+ "examples": {
+ "setCaseConfigResponse": {
+ "$ref": "#/components/examples/set_case_configuration_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/configure/{configurationId}": {
+ "patch": {
+ "summary": "Updates external connection details, such as the closure type and default connector for cases in the default space.",
+ "operationId": "updateCaseConfigurationDefaultSpace",
+ "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ },
+ {
+ "$ref": "#/components/parameters/configuration_id"
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/update_case_configuration_request"
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "closure_type": {
+ "$ref": "#/components/schemas/closure_types"
+ },
+ "connector": {
+ "type": "object",
+ "properties": {
+ "fields": {
+ "description": "The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.",
+ "nullable": true,
+ "type": "object"
+ },
+ "id": {
+ "description": "The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.",
+ "type": "string",
+ "example": "none"
+ },
+ "name": {
+ "description": "The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.",
+ "type": "string",
+ "example": "none"
+ },
+ "type": {
+ "$ref": "#/components/schemas/connector_types"
+ }
+ }
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time",
+ "example": "2022-06-01T17:07:17.767Z"
+ },
+ "created_by": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "full_name": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "username": {
+ "type": "string",
+ "example": "elastic",
+ "nullable": true
+ },
+ "profile_uid": {
+ "type": "string",
+ "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0"
+ }
+ }
+ },
+ "error": {
+ "type": "string",
+ "nullable": true,
+ "example": null
+ },
+ "id": {
+ "type": "string",
+ "example": "4a97a440-e1cd-11ec-be9b-9b1838238ee6"
+ },
+ "mappings": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "action_type": {
+ "type": "string",
+ "example": "overwrite"
+ },
+ "source": {
+ "type": "string",
+ "example": "title"
+ },
+ "target": {
+ "type": "string",
+ "example": "summary"
+ }
+ }
+ }
+ },
+ "owner": {
+ "$ref": "#/components/schemas/owners"
+ },
+ "updated_at": {
+ "type": "string",
+ "format": "date-time",
+ "nullable": true,
+ "example": "2022-06-01T19:58:48.169Z"
+ },
+ "updated_by": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "full_name": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "username": {
+ "type": "string",
+ "example": "elastic",
+ "nullable": true
+ },
+ "profile_uid": {
+ "type": "string",
+ "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0"
+ }
+ },
+ "nullable": true
+ },
+ "version": {
+ "type": "string",
+ "example": "WzIwNzMsMV0="
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/reporters": {
+ "get": {
+ "summary": "Returns information about the users who opened cases in the default space.",
+ "operationId": "getCaseReportersDefaultSpace",
+ "description": "You must have read privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases. The API returns information about the users as they existed at the time of the case creation, including their name, full name, and email address. If any of those details change thereafter or if a user is deleted, the information returned by this API is unchanged.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/owner"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "full_name": {
+ "type": "string",
+ "example": null,
+ "nullable": true
+ },
+ "username": {
+ "type": "string",
+ "example": "elastic",
+ "nullable": true
+ },
+ "profile_uid": {
+ "type": "string",
+ "example": "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0"
+ }
+ }
+ }
+ },
+ "examples": {
+ "getReportersResponse": {
+ "$ref": "#/components/examples/get_reporters_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/status": {
+ "get": {
+ "summary": "Returns the number of cases that are open, closed, and in progress in the default space.",
+ "operationId": "getCaseStatusDefaultSpace",
+ "description": "Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n",
+ "deprecated": true,
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/owner"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "count_closed_cases": {
+ "type": "integer"
+ },
+ "count_in_progress_cases": {
+ "type": "integer"
+ },
+ "count_open_cases": {
+ "type": "integer"
+ }
+ }
+ },
+ "examples": {
+ "getStatusResponse": {
+ "$ref": "#/components/examples/get_status_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/tags": {
+ "get": {
+ "summary": "Aggregates and returns a list of case tags in the default space.",
+ "operationId": "getCaseTagsDefaultSpace",
+ "description": "You must have read privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/owner"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "examples": {
+ "getTagsResponse": {
+ "$ref": "#/components/examples/get_tags_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/{caseId}": {
+ "get": {
+ "summary": "Retrieves information about a case in the default space.",
+ "operationId": "getCaseDefaultSpace",
+ "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/case_id"
+ },
+ {
+ "$ref": "#/components/parameters/includeComments"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/case_response_properties"
+ },
+ "examples": {
+ "getCaseResponse": {
+ "$ref": "#/components/examples/get_case_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/{caseId}/alerts": {
+ "get": {
+ "summary": "Gets all alerts attached to a case in the default space.",
+ "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n",
+ "x-technical-preview": true,
+ "operationId": "getCaseAlertsDefaultSpace",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/case_id"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/alert_response_properties"
+ }
+ },
+ "examples": {
+ "getCaseAlertsResponse": {
+ "$ref": "#/components/examples/get_case_alerts_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/{caseId}/comments": {
+ "post": {
+ "summary": "Adds a comment or alert to a case in the default space.",
+ "operationId": "addCaseCommentDefaultSpace",
+ "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating. NOTE: Each case can have a maximum of 1,000 alerts.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ },
+ {
+ "$ref": "#/components/parameters/case_id"
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/add_case_comment_request"
+ },
+ "examples": {
+ "createCaseCommentRequest": {
+ "$ref": "#/components/examples/add_comment_request"
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/case_response_properties"
+ },
+ "examples": {
+ "createCaseCommentResponse": {
+ "$ref": "#/components/examples/add_comment_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "delete": {
+ "summary": "Deletes all comments and alerts from a case in the default space.",
+ "operationId": "deleteCaseCommentsDefaultSpace",
+ "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ },
+ {
+ "$ref": "#/components/parameters/case_id"
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Indicates a successful call."
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "patch": {
+ "summary": "Updates a comment or alert in a case in the default space.",
+ "operationId": "updateCaseCommentDefaultSpace",
+ "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ },
+ {
+ "$ref": "#/components/parameters/case_id"
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/update_case_comment_request"
+ },
+ "examples": {
+ "updateCaseCommentRequest": {
+ "$ref": "#/components/examples/update_comment_request"
+ }
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/case_response_properties"
+ },
+ "examples": {
+ "updateCaseCommentResponse": {
+ "$ref": "#/components/examples/update_comment_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "get": {
+ "summary": "Retrieves all the comments from a case in the default space.",
+ "operationId": "getAllCaseCommentsDefaultSpace",
+ "description": "Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; instead, use the get case comment API, which requires a comment identifier in the path. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n",
+ "deprecated": true,
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/case_id"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/case_response_properties"
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/{caseId}/comments/{commentId}": {
+ "delete": {
+ "summary": "Deletes a comment or alert from a case in the default space.",
+ "operationId": "deleteCaseCommentDefaultSpace",
+ "description": "You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ },
+ {
+ "$ref": "#/components/parameters/case_id"
+ },
+ {
+ "$ref": "#/components/parameters/comment_id"
+ }
+ ],
+ "responses": {
+ "204": {
+ "description": "Indicates a successful call."
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "get": {
+ "summary": "Retrieves a comment from a case in the default space.",
+ "operationId": "getCaseCommentDefaultSpace",
+ "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.\n",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/case_id"
+ },
+ {
+ "$ref": "#/components/parameters/comment_id"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/alert_comment_response_properties"
+ },
+ {
+ "$ref": "#/components/schemas/user_comment_response_properties"
+ }
+ ]
+ },
+ "examples": {
+ "getCaseCommentResponse": {
+ "$ref": "#/components/examples/get_comment_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/{caseId}/connector/{connectorId}/_push": {
+ "post": {
+ "summary": "Pushes a case in the default space to an external service.",
+ "description": "You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. You must also have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're pushing.\n",
+ "operationId": "pushCaseDefaultSpace",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/case_id"
+ },
+ {
+ "$ref": "#/components/parameters/connector_id"
+ },
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "nullable": true
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/case_response_properties"
+ },
+ "examples": {
+ "pushCaseResponse": {
+ "$ref": "#/components/examples/push_case_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/{caseId}/user_actions": {
+ "get": {
+ "summary": "Returns all user activity for a case in the default space.",
+ "description": "Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.\n",
+ "deprecated": true,
+ "operationId": "getCaseActivityDefaultSpace",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/case_id"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/user_actions_response_properties"
+ }
+ },
+ "examples": {
+ "getCaseActivityResponse": {
+ "$ref": "#/components/examples/get_case_activity_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/{caseId}/user_actions/_find": {
+ "get": {
+ "summary": "Finds user activity for a case in the default space.",
+ "description": "You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.\n",
+ "operationId": "findCaseActivityDefaultSpace",
+ "tags": [
+ "cases"
+ ],
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/case_id"
+ },
+ {
+ "$ref": "#/components/parameters/page_index"
+ },
+ {
+ "$ref": "#/components/parameters/page_size"
+ },
+ {
+ "$ref": "#/components/parameters/sort_order"
+ },
+ {
+ "$ref": "#/components/parameters/user_action_types"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "page": {
+ "type": "integer"
+ },
+ "perPage": {
+ "type": "integer"
+ },
+ "total": {
+ "type": "integer"
+ },
+ "userActions": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/user_actions_find_response_properties"
+ }
+ }
+ }
+ },
+ "examples": {
+ "findCaseActivityResponse": {
+ "$ref": "#/components/examples/find_case_activity_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "/api/cases/configure/connectors/_find": {
+ "get": {
+ "summary": "Retrieves information about connectors in the default space.",
+ "operationId": "findCaseConnectorsDefaultSpace",
+ "description": "In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.\n",
+ "tags": [
+ "cases"
+ ],
+ "responses": {
+ "200": {
+ "description": "Indicates a successful call.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "actionTypeId": {
+ "$ref": "#/components/schemas/connector_types"
+ },
+ "config": {
+ "type": "object",
+ "properties": {
+ "apiUrl": {
+ "type": "string"
+ },
+ "projectKey": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": true
+ },
+ "id": {
+ "type": "string"
+ },
+ "isDeprecated": {
+ "type": "boolean"
+ },
+ "isMissingSecrets": {
+ "type": "boolean"
+ },
+ "isPreconfigured": {
+ "type": "boolean"
+ },
+ "name": {
+ "type": "string"
+ },
+ "referencedByCount": {
+ "type": "integer"
+ }
+ }
+ }
+ },
+ "examples": {
+ "findConnectorResponse": {
+ "$ref": "#/components/examples/find_connector_response"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authorization information is missing or invalid.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/4xx_response"
+ }
+ }
+ }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
"/s/{spaceId}/api/cases": {
"post": {
"summary": "Creates a case.",
@@ -101,17 +1909,10 @@
"$ref": "#/components/parameters/kbn_xsrf"
},
{
- "$ref": "#/components/parameters/space_id"
+ "$ref": "#/components/parameters/ids"
},
{
- "name": "ids",
- "description": "The cases that you want to removed. All non-ASCII characters must be URL encoded.",
- "in": "query",
- "required": true,
- "schema": {
- "type": "string"
- },
- "example": "d4e7abb0-b462-11ec-9a8d-698504725a43"
+ "$ref": "#/components/parameters/space_id"
}
],
"responses": {
@@ -219,61 +2020,16 @@
"$ref": "#/components/parameters/space_id"
},
{
- "name": "assignees",
- "in": "query",
- "description": "Filters the returned cases by assignees. Valid values are `none` or unique identifiers for the user profiles. These identifiers can be found by using the suggest user profile API.\n",
- "schema": {
- "oneOf": [
- {
- "type": "string"
- },
- {
- "type": "array",
- "items": {
- "type": "string"
- },
- "maxItems": 100
- }
- ]
- }
+ "$ref": "#/components/parameters/assignees"
},
{
- "name": "category",
- "in": "query",
- "description": "Filters the returned cases by category. Limited to 100 categories.",
- "schema": {
- "oneOf": [
- {
- "type": "string"
- },
- {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- ]
- },
- "example": "my-category"
+ "$ref": "#/components/parameters/category"
},
{
- "name": "defaultSearchOperator",
- "in": "query",
- "description": "The default operator to use for the simple_query_string.",
- "schema": {
- "type": "string",
- "default": "OR"
- },
- "example": "OR"
+ "$ref": "#/components/parameters/defaultSearchOperator"
},
{
- "name": "from",
- "in": "query",
- "description": "[preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n",
- "schema": {
- "type": "string"
- },
- "example": "now-1d"
+ "$ref": "#/components/parameters/from"
},
{
"$ref": "#/components/parameters/owner"
@@ -285,113 +2041,31 @@
"$ref": "#/components/parameters/page_size"
},
{
- "name": "reporters",
- "in": "query",
- "description": "Filters the returned cases by the user name of the reporter.",
- "schema": {
- "oneOf": [
- {
- "type": "string"
- },
- {
- "type": "array",
- "items": {
- "type": "string"
- },
- "maxItems": 100
- }
- ]
- },
- "example": "elastic"
+ "$ref": "#/components/parameters/reporters"
},
{
- "name": "search",
- "in": "query",
- "description": "An Elasticsearch simple_query_string query that filters the objects in the response.",
- "schema": {
- "type": "string"
- }
+ "$ref": "#/components/parameters/search"
},
{
- "name": "searchFields",
- "in": "query",
- "description": "The fields to perform the simple_query_string parsed query against.",
- "schema": {
- "oneOf": [
- {
- "$ref": "#/components/schemas/search_fields"
- },
- {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/search_fields"
- }
- }
- ]
- }
+ "$ref": "#/components/parameters/searchFields"
},
{
"$ref": "#/components/parameters/severity"
},
{
- "name": "sortField",
- "in": "query",
- "description": "Determines which field is used to sort the results.",
- "schema": {
- "type": "string",
- "enum": [
- "createdAt",
- "updatedAt"
- ],
- "default": "createdAt"
- },
- "example": "updatedAt"
+ "$ref": "#/components/parameters/sortField"
},
{
"$ref": "#/components/parameters/sort_order"
},
{
- "name": "status",
- "in": "query",
- "description": "Filters the returned cases by state.",
- "schema": {
- "type": "string",
- "enum": [
- "closed",
- "in-progress",
- "open"
- ]
- },
- "example": "open"
+ "$ref": "#/components/parameters/status"
},
{
- "name": "tags",
- "in": "query",
- "description": "Filters the returned cases by tags.",
- "schema": {
- "oneOf": [
- {
- "type": "string"
- },
- {
- "type": "array",
- "items": {
- "type": "string"
- },
- "maxItems": 100
- }
- ]
- },
- "example": "tag-1"
+ "$ref": "#/components/parameters/tags"
},
{
- "name": "to",
- "in": "query",
- "description": "[preview] Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n",
- "schema": {
- "type": "string"
- },
- "example": "now+1d"
+ "$ref": "#/components/parameters/to"
}
],
"responses": {
@@ -722,64 +2396,7 @@
"content": {
"application/json": {
"schema": {
- "type": "object",
- "properties": {
- "closure_type": {
- "$ref": "#/components/schemas/closure_types"
- },
- "connector": {
- "description": "An object that contains the connector configuration.",
- "type": "object",
- "properties": {
- "fields": {
- "description": "The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.",
- "nullable": true,
- "type": "object"
- },
- "id": {
- "description": "The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.",
- "type": "string",
- "example": "none"
- },
- "name": {
- "description": "The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.",
- "type": "string",
- "example": "none"
- },
- "type": {
- "$ref": "#/components/schemas/connector_types"
- }
- },
- "required": [
- "fields",
- "id",
- "name",
- "type"
- ]
- },
- "owner": {
- "$ref": "#/components/schemas/owners"
- },
- "settings": {
- "description": "An object that contains the case settings.",
- "type": "object",
- "properties": {
- "syncAlerts": {
- "description": "Turns alert syncing on or off.",
- "type": "boolean",
- "example": true
- }
- },
- "required": [
- "syncAlerts"
- ]
- }
- },
- "required": [
- "closure_type",
- "connector",
- "owner"
- ]
+ "$ref": "#/components/schemas/set_case_configuration_request"
},
"examples": {
"setCaseConfigRequest": {
@@ -975,50 +2592,7 @@
"content": {
"application/json": {
"schema": {
- "type": "object",
- "properties": {
- "closure_type": {
- "$ref": "#/components/schemas/closure_types"
- },
- "connector": {
- "description": "An object that contains the connector configuration.",
- "type": "object",
- "properties": {
- "fields": {
- "description": "The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.",
- "nullable": true,
- "type": "object"
- },
- "id": {
- "description": "The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.",
- "type": "string",
- "example": "none"
- },
- "name": {
- "description": "The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.",
- "type": "string",
- "example": "none"
- },
- "type": {
- "$ref": "#/components/schemas/connector_types"
- }
- },
- "required": [
- "fields",
- "id",
- "name",
- "type"
- ]
- },
- "version": {
- "description": "The version of the connector. To retrieve the version value, use the get configuration API.\n",
- "type": "string",
- "example": "WzIwMiwxXQ=="
- }
- },
- "required": [
- "version"
- ]
+ "$ref": "#/components/schemas/update_case_configuration_request"
}
}
}
@@ -1433,22 +3007,7 @@
"$ref": "#/components/parameters/space_id"
},
{
- "in": "query",
- "name": "owner",
- "description": "A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read.",
- "schema": {
- "oneOf": [
- {
- "$ref": "#/components/schemas/owners"
- },
- {
- "type": "array",
- "items": {
- "$ref": "#/components/schemas/owners"
- }
- }
- ]
- }
+ "$ref": "#/components/parameters/owner"
}
],
"responses": {
@@ -1509,14 +3068,7 @@
"$ref": "#/components/parameters/space_id"
},
{
- "in": "query",
- "name": "includeComments",
- "description": "Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned.",
- "deprecated": true,
- "schema": {
- "type": "boolean",
- "default": true
- }
+ "$ref": "#/components/parameters/includeComments"
}
],
"responses": {
@@ -2169,33 +3721,7 @@
"$ref": "#/components/parameters/sort_order"
},
{
- "name": "types",
- "in": "query",
- "description": "Determines the types of user actions to return.",
- "schema": {
- "type": "array",
- "items": {
- "type": "string",
- "enum": [
- "action",
- "alert",
- "assignees",
- "attachment",
- "comment",
- "connector",
- "create_case",
- "description",
- "pushed",
- "settings",
- "severity",
- "status",
- "tags",
- "title",
- "user"
- ]
- }
- },
- "example": "create_case"
+ "$ref": "#/components/parameters/user_action_types"
}
],
"responses": {
@@ -2240,51 +3766,109 @@
}
}
}
- }
+ }
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ },
+ "servers": [
+ {
+ "url": "https://localhost:5601"
+ }
+ ]
+ }
+ },
+ "components": {
+ "securitySchemes": {
+ "basicAuth": {
+ "type": "http",
+ "scheme": "basic"
+ },
+ "apiKeyAuth": {
+ "type": "apiKey",
+ "in": "header",
+ "name": "ApiKey"
+ }
+ },
+ "parameters": {
+ "kbn_xsrf": {
+ "schema": {
+ "type": "string"
+ },
+ "in": "header",
+ "name": "kbn-xsrf",
+ "description": "Cross-site request forgery protection",
+ "required": true
+ },
+ "ids": {
+ "name": "ids",
+ "description": "The cases that you want to removed. All non-ASCII characters must be URL encoded.\n",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "type": "string"
+ },
+ "example": "d4e7abb0-b462-11ec-9a8d-698504725a43"
+ },
+ "assignees": {
+ "in": "query",
+ "name": "assignees",
+ "description": "Filters the returned cases by assignees. Valid values are `none` or unique identifiers for the user profiles. These identifiers can be found by using the suggest user profile API.\n",
+ "schema": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 100
+ }
+ ]
+ }
+ },
+ "category": {
+ "in": "query",
+ "name": "category",
+ "description": "Filters the returned cases by category.",
+ "schema": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 100
+ }
+ ]
},
- "servers": [
- {
- "url": "https://localhost:5601"
- }
- ]
- },
- "servers": [
- {
- "url": "https://localhost:5601"
- }
- ]
- }
- },
- "components": {
- "securitySchemes": {
- "basicAuth": {
- "type": "http",
- "scheme": "basic"
+ "example": "my-category"
},
- "apiKeyAuth": {
- "type": "apiKey",
- "in": "header",
- "name": "ApiKey"
- }
- },
- "parameters": {
- "kbn_xsrf": {
+ "defaultSearchOperator": {
+ "in": "query",
+ "name": "defaultSearchOperator",
+ "description": "he default operator to use for the simple_query_string.",
"schema": {
- "type": "string"
+ "type": "string",
+ "default": "OR"
},
- "in": "header",
- "name": "kbn-xsrf",
- "description": "Cross-site request forgery protection",
- "required": true
+ "example": "OR"
},
- "space_id": {
- "in": "path",
- "name": "spaceId",
- "description": "An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.",
- "required": true,
+ "from": {
+ "in": "query",
+ "name": "from",
+ "description": "[preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n",
"schema": {
"type": "string",
- "example": "default"
+ "example": "now-1d"
}
},
"owner": {
@@ -2326,6 +3910,52 @@
"default": 20
}
},
+ "reporters": {
+ "in": "query",
+ "name": "reporters",
+ "description": "Filters the returned cases by the user name of the reporter.",
+ "schema": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 100
+ }
+ ]
+ },
+ "example": "elastic"
+ },
+ "search": {
+ "in": "query",
+ "name": "search",
+ "description": "An Elasticsearch simple_query_string query that filters the objects in the response.",
+ "schema": {
+ "type": "string"
+ }
+ },
+ "searchFields": {
+ "in": "query",
+ "name": "searchFields",
+ "description": "The fields to perform the simple_query_string parsed query against.",
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/searchFieldsType"
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/searchFieldsType"
+ }
+ }
+ ]
+ }
+ },
"severity": {
"in": "query",
"name": "severity",
@@ -2340,6 +3970,20 @@
]
}
},
+ "sortField": {
+ "in": "query",
+ "name": "sortField",
+ "description": "Determines which field is used to sort the results.",
+ "schema": {
+ "type": "string",
+ "enum": [
+ "createdAt",
+ "updatedAt"
+ ],
+ "default": "createdAt"
+ },
+ "example": "updatedAt"
+ },
"sort_order": {
"in": "query",
"name": "sortOrder",
@@ -2354,6 +3998,49 @@
"default": "desc"
}
},
+ "status": {
+ "in": "query",
+ "name": "status",
+ "description": "Filters the returned cases by state.",
+ "schema": {
+ "type": "string",
+ "enum": [
+ "closed",
+ "in-progress",
+ "open"
+ ]
+ },
+ "example": "open"
+ },
+ "tags": {
+ "in": "query",
+ "name": "tags",
+ "description": "Filters the returned cases by tags.",
+ "schema": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 100
+ }
+ ]
+ },
+ "example": "tag-1"
+ },
+ "to": {
+ "in": "query",
+ "name": "to",
+ "description": "[preview] Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.\n",
+ "schema": {
+ "type": "string"
+ },
+ "example": "now+1d"
+ },
"alert_id": {
"in": "path",
"name": "alertId",
@@ -2384,6 +4071,16 @@
"example": "9c235210-6834-11ea-a78c-6ffb38a34414"
}
},
+ "includeComments": {
+ "in": "query",
+ "name": "includeComments",
+ "description": "Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned.",
+ "deprecated": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ },
"comment_id": {
"in": "path",
"name": "commentId",
@@ -2403,6 +4100,45 @@
"type": "string",
"example": "abed3a70-71bd-11ea-a0b2-c51ea50a58e2"
}
+ },
+ "user_action_types": {
+ "in": "query",
+ "name": "types",
+ "description": "Determines the types of user actions to return.",
+ "schema": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "action",
+ "alert",
+ "assignees",
+ "attachment",
+ "comment",
+ "connector",
+ "create_case",
+ "description",
+ "pushed",
+ "settings",
+ "severity",
+ "status",
+ "tags",
+ "title",
+ "user"
+ ]
+ }
+ },
+ "example": "create_case"
+ },
+ "space_id": {
+ "in": "path",
+ "name": "spaceId",
+ "description": "An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "example": "default"
+ }
}
},
"schemas": {
@@ -3527,7 +5263,7 @@
}
}
},
- "search_fields": {
+ "searchFieldsType": {
"type": "string",
"description": "The fields to perform the `simple_query_string` parsed query against.",
"enum": [
@@ -3581,6 +5317,116 @@
],
"example": ".none"
},
+ "set_case_configuration_request": {
+ "title": "Set case configuration request",
+ "description": "External connection details, such as the closure type and default connector for cases.",
+ "type": "object",
+ "required": [
+ "closure_type",
+ "connector",
+ "owner"
+ ],
+ "properties": {
+ "closure_type": {
+ "$ref": "#/components/schemas/closure_types"
+ },
+ "connector": {
+ "description": "An object that contains the connector configuration.",
+ "type": "object",
+ "properties": {
+ "fields": {
+ "description": "The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.",
+ "nullable": true,
+ "type": "object"
+ },
+ "id": {
+ "description": "The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.",
+ "type": "string",
+ "example": "none"
+ },
+ "name": {
+ "description": "The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.",
+ "type": "string",
+ "example": "none"
+ },
+ "type": {
+ "$ref": "#/components/schemas/connector_types"
+ }
+ },
+ "required": [
+ "fields",
+ "id",
+ "name",
+ "type"
+ ]
+ },
+ "owner": {
+ "$ref": "#/components/schemas/owners"
+ },
+ "settings": {
+ "description": "An object that contains the case settings.",
+ "type": "object",
+ "properties": {
+ "syncAlerts": {
+ "description": "Turns alert syncing on or off.",
+ "type": "boolean",
+ "example": true
+ }
+ },
+ "required": [
+ "syncAlerts"
+ ]
+ }
+ }
+ },
+ "update_case_configuration_request": {
+ "title": "Update case configuration request",
+ "description": "External connection details, such as the closure type and default connector for cases.",
+ "type": "object",
+ "required": [
+ "version"
+ ],
+ "properties": {
+ "closure_type": {
+ "$ref": "#/components/schemas/closure_types"
+ },
+ "connector": {
+ "description": "An object that contains the connector configuration.",
+ "type": "object",
+ "properties": {
+ "fields": {
+ "description": "The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.",
+ "nullable": true,
+ "type": "object"
+ },
+ "id": {
+ "description": "The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.",
+ "type": "string",
+ "example": "none"
+ },
+ "name": {
+ "description": "The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.",
+ "type": "string",
+ "example": "none"
+ },
+ "type": {
+ "$ref": "#/components/schemas/connector_types"
+ }
+ },
+ "required": [
+ "fields",
+ "id",
+ "name",
+ "type"
+ ]
+ },
+ "version": {
+ "description": "The version of the connector. To retrieve the version value, use the get configuration API.\n",
+ "type": "string",
+ "example": "WzIwMiwxXQ=="
+ }
+ }
+ },
"alert_response_properties": {
"type": "object",
"properties": {
@@ -4755,24 +6601,6 @@
"id": "4a97a440-e1cd-11ec-be9b-9b1838238ee6"
}
},
- "find_connector_response": {
- "summary": "Retrieve information about the connectors and their settings.",
- "value": [
- {
- "id": "61787f53-4eee-4741-8df6-8fe84fa616f7",
- "actionTypeId": ".jira",
- "name": "my-Jira",
- "isMissingSecrets": false,
- "config": {
- "apiUrl": "https://elastic.atlassian.net/",
- "projectKey": "ES"
- },
- "isPreconfigured": false,
- "isDeprecated": false,
- "referencedByCount": 0
- }
- ]
- },
"get_reporters_response": {
"summary": "A list of three users that opened cases",
"value": [
@@ -5282,6 +7110,24 @@
}
]
}
+ },
+ "find_connector_response": {
+ "summary": "Retrieve information about the connectors and their settings.",
+ "value": [
+ {
+ "id": "61787f53-4eee-4741-8df6-8fe84fa616f7",
+ "actionTypeId": ".jira",
+ "name": "my-Jira",
+ "isMissingSecrets": false,
+ "config": {
+ "apiUrl": "https://elastic.atlassian.net/",
+ "projectKey": "ES"
+ },
+ "isPreconfigured": false,
+ "isDeprecated": false,
+ "referencedByCount": 0
+ }
+ ]
}
}
},
diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml
index 30ca1177c1390..50184a7b96742 100644
--- a/x-pack/plugins/cases/docs/openapi/bundled.yaml
+++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml
@@ -15,6 +15,1110 @@ servers:
- url: http://localhost:5601
description: local
paths:
+ /api/cases:
+ post:
+ summary: Creates a case in the default space.
+ operationId: createCaseDefaultSpace
+ description: |
+ You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/kbn_xsrf'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/create_case_request'
+ examples:
+ createCaseRequest:
+ $ref: '#/components/examples/create_case_request'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/case_response_properties'
+ examples:
+ createCaseResponse:
+ $ref: '#/components/examples/create_case_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ delete:
+ summary: Deletes one or more cases in the default space.
+ operationId: deleteCaseDefaultSpace
+ description: |
+ You must have `read` or `all` privileges and the `delete` sub-feature privilege for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/kbn_xsrf'
+ - $ref: '#/components/parameters/ids'
+ responses:
+ '204':
+ description: Indicates a successful call.
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ patch:
+ summary: Updates one or more cases in the default space.
+ operationId: updateCaseDefaultSpace
+ description: |
+ You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/kbn_xsrf'
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/update_case_request'
+ examples:
+ updateCaseRequest:
+ $ref: '#/components/examples/update_case_request'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/case_response_properties'
+ examples:
+ updateCaseResponse:
+ $ref: '#/components/examples/update_case_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/_find:
+ get:
+ summary: Retrieves a paginated subset of cases in the default space.
+ operationId: findCasesDefaultSpace
+ description: |
+ You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/assignees'
+ - $ref: '#/components/parameters/category'
+ - $ref: '#/components/parameters/defaultSearchOperator'
+ - $ref: '#/components/parameters/from'
+ - $ref: '#/components/parameters/owner'
+ - $ref: '#/components/parameters/page_index'
+ - $ref: '#/components/parameters/page_size'
+ - $ref: '#/components/parameters/reporters'
+ - $ref: '#/components/parameters/search'
+ - $ref: '#/components/parameters/searchFields'
+ - $ref: '#/components/parameters/severity'
+ - $ref: '#/components/parameters/sortField'
+ - $ref: '#/components/parameters/sort_order'
+ - $ref: '#/components/parameters/status'
+ - $ref: '#/components/parameters/tags'
+ - $ref: '#/components/parameters/to'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ cases:
+ type: array
+ items:
+ $ref: '#/components/schemas/case_response_properties'
+ count_closed_cases:
+ type: integer
+ count_in_progress_cases:
+ type: integer
+ count_open_cases:
+ type: integer
+ page:
+ type: integer
+ per_page:
+ type: integer
+ total:
+ type: integer
+ examples:
+ findCaseResponse:
+ $ref: '#/components/examples/find_case_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/alerts/{alertId}:
+ get:
+ summary: Returns the cases associated with a specific alert in the default space.
+ operationId: getCasesByAlertDefaultSpace
+ description: |
+ You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+ x-technical-preview: true
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/alert_id'
+ - $ref: '#/components/parameters/owner'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ description: The case identifier.
+ title:
+ type: string
+ description: The case title.
+ example:
+ - id: 06116b80-e1c3-11ec-be9b-9b1838238ee6
+ title: security_case
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601s
+ /api/cases/configure:
+ get:
+ summary: Retrieves external connection details, such as the closure type and default connector for cases in the default space.
+ operationId: getCaseConfigurationDefaultSpace
+ description: |
+ You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/owner'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ closure_type:
+ $ref: '#/components/schemas/closure_types'
+ connector:
+ type: object
+ properties:
+ fields:
+ description: The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.
+ nullable: true
+ type: object
+ id:
+ description: The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.
+ type: string
+ example: none
+ name:
+ description: The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.
+ type: string
+ example: none
+ type:
+ $ref: '#/components/schemas/connector_types'
+ created_at:
+ type: string
+ format: date-time
+ example: '2022-06-01T17:07:17.767Z'
+ created_by:
+ type: object
+ properties:
+ email:
+ type: string
+ example: null
+ nullable: true
+ full_name:
+ type: string
+ example: null
+ nullable: true
+ username:
+ type: string
+ example: elastic
+ nullable: true
+ profile_uid:
+ type: string
+ example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0
+ error:
+ type: string
+ nullable: true
+ example: null
+ id:
+ type: string
+ example: 4a97a440-e1cd-11ec-be9b-9b1838238ee6
+ mappings:
+ type: array
+ items:
+ type: object
+ properties:
+ action_type:
+ type: string
+ example: overwrite
+ source:
+ type: string
+ example: title
+ target:
+ type: string
+ example: summary
+ owner:
+ $ref: '#/components/schemas/owners'
+ updated_at:
+ type: string
+ format: date-time
+ nullable: true
+ example: '2022-06-01T19:58:48.169Z'
+ updated_by:
+ type: object
+ properties:
+ email:
+ type: string
+ example: null
+ nullable: true
+ full_name:
+ type: string
+ example: null
+ nullable: true
+ username:
+ type: string
+ example: elastic
+ nullable: true
+ profile_uid:
+ type: string
+ example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0
+ nullable: true
+ version:
+ type: string
+ example: WzIwNzMsMV0=
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ post:
+ summary: Sets external connection details, such as the closure type and default connector for cases in the default space.
+ operationId: setCaseConfigurationDefaultSpace
+ description: |
+ You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API. If you set a default connector, it is automatically selected when you create cases in Kibana. If you use the create case API, however, you must still specify all of the connector details.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/kbn_xsrf'
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/set_case_configuration_request'
+ examples:
+ setCaseConfigRequest:
+ $ref: '#/components/examples/set_case_configuration_request'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ closure_type:
+ $ref: '#/components/schemas/closure_types'
+ connector:
+ type: object
+ properties:
+ fields:
+ description: The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.
+ nullable: true
+ type: object
+ id:
+ description: The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.
+ type: string
+ example: none
+ name:
+ description: The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.
+ type: string
+ example: none
+ type:
+ $ref: '#/components/schemas/connector_types'
+ created_at:
+ type: string
+ format: date-time
+ example: '2022-06-01T17:07:17.767Z'
+ created_by:
+ type: object
+ properties:
+ email:
+ type: string
+ example: null
+ nullable: true
+ full_name:
+ type: string
+ example: null
+ nullable: true
+ username:
+ type: string
+ example: elastic
+ nullable: true
+ profile_uid:
+ type: string
+ example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0
+ error:
+ type: string
+ nullable: true
+ example: null
+ id:
+ type: string
+ example: 4a97a440-e1cd-11ec-be9b-9b1838238ee6
+ mappings:
+ type: array
+ items:
+ type: object
+ properties:
+ action_type:
+ type: string
+ example: overwrite
+ source:
+ type: string
+ example: title
+ target:
+ type: string
+ example: summary
+ owner:
+ $ref: '#/components/schemas/owners'
+ updated_at:
+ type: string
+ format: date-time
+ nullable: true
+ example: '2022-06-01T19:58:48.169Z'
+ updated_by:
+ type: object
+ properties:
+ email:
+ type: string
+ example: null
+ nullable: true
+ full_name:
+ type: string
+ example: null
+ nullable: true
+ username:
+ type: string
+ example: elastic
+ nullable: true
+ profile_uid:
+ type: string
+ example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0
+ nullable: true
+ version:
+ type: string
+ example: WzIwNzMsMV0=
+ examples:
+ setCaseConfigResponse:
+ $ref: '#/components/examples/set_case_configuration_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/configure/{configurationId}:
+ patch:
+ summary: Updates external connection details, such as the closure type and default connector for cases in the default space.
+ operationId: updateCaseConfigurationDefaultSpace
+ description: |
+ You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration. Connectors are used to interface with external systems. You must create a connector before you can use it in your cases. Refer to the add connectors API.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/kbn_xsrf'
+ - $ref: '#/components/parameters/configuration_id'
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/update_case_configuration_request'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ closure_type:
+ $ref: '#/components/schemas/closure_types'
+ connector:
+ type: object
+ properties:
+ fields:
+ description: The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.
+ nullable: true
+ type: object
+ id:
+ description: The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.
+ type: string
+ example: none
+ name:
+ description: The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.
+ type: string
+ example: none
+ type:
+ $ref: '#/components/schemas/connector_types'
+ created_at:
+ type: string
+ format: date-time
+ example: '2022-06-01T17:07:17.767Z'
+ created_by:
+ type: object
+ properties:
+ email:
+ type: string
+ example: null
+ nullable: true
+ full_name:
+ type: string
+ example: null
+ nullable: true
+ username:
+ type: string
+ example: elastic
+ nullable: true
+ profile_uid:
+ type: string
+ example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0
+ error:
+ type: string
+ nullable: true
+ example: null
+ id:
+ type: string
+ example: 4a97a440-e1cd-11ec-be9b-9b1838238ee6
+ mappings:
+ type: array
+ items:
+ type: object
+ properties:
+ action_type:
+ type: string
+ example: overwrite
+ source:
+ type: string
+ example: title
+ target:
+ type: string
+ example: summary
+ owner:
+ $ref: '#/components/schemas/owners'
+ updated_at:
+ type: string
+ format: date-time
+ nullable: true
+ example: '2022-06-01T19:58:48.169Z'
+ updated_by:
+ type: object
+ properties:
+ email:
+ type: string
+ example: null
+ nullable: true
+ full_name:
+ type: string
+ example: null
+ nullable: true
+ username:
+ type: string
+ example: elastic
+ nullable: true
+ profile_uid:
+ type: string
+ example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0
+ nullable: true
+ version:
+ type: string
+ example: WzIwNzMsMV0=
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/reporters:
+ get:
+ summary: Returns information about the users who opened cases in the default space.
+ operationId: getCaseReportersDefaultSpace
+ description: |
+ You must have read privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases. The API returns information about the users as they existed at the time of the case creation, including their name, full name, and email address. If any of those details change thereafter or if a user is deleted, the information returned by this API is unchanged.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/owner'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ email:
+ type: string
+ example: null
+ nullable: true
+ full_name:
+ type: string
+ example: null
+ nullable: true
+ username:
+ type: string
+ example: elastic
+ nullable: true
+ profile_uid:
+ type: string
+ example: u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0
+ examples:
+ getReportersResponse:
+ $ref: '#/components/examples/get_reporters_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/status:
+ get:
+ summary: Returns the number of cases that are open, closed, and in progress in the default space.
+ operationId: getCaseStatusDefaultSpace
+ description: |
+ Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+ deprecated: true
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/owner'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ count_closed_cases:
+ type: integer
+ count_in_progress_cases:
+ type: integer
+ count_open_cases:
+ type: integer
+ examples:
+ getStatusResponse:
+ $ref: '#/components/examples/get_status_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/tags:
+ get:
+ summary: Aggregates and returns a list of case tags in the default space.
+ operationId: getCaseTagsDefaultSpace
+ description: |
+ You must have read privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/owner'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ examples:
+ getTagsResponse:
+ $ref: '#/components/examples/get_tags_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/{caseId}:
+ get:
+ summary: Retrieves information about a case in the default space.
+ operationId: getCaseDefaultSpace
+ description: |
+ You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/case_id'
+ - $ref: '#/components/parameters/includeComments'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/case_response_properties'
+ examples:
+ getCaseResponse:
+ $ref: '#/components/examples/get_case_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/{caseId}/alerts:
+ get:
+ summary: Gets all alerts attached to a case in the default space.
+ description: |
+ You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+ x-technical-preview: true
+ operationId: getCaseAlertsDefaultSpace
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/case_id'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/alert_response_properties'
+ examples:
+ getCaseAlertsResponse:
+ $ref: '#/components/examples/get_case_alerts_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/{caseId}/comments:
+ post:
+ summary: Adds a comment or alert to a case in the default space.
+ operationId: addCaseCommentDefaultSpace
+ description: |
+ You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're creating. NOTE: Each case can have a maximum of 1,000 alerts.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/kbn_xsrf'
+ - $ref: '#/components/parameters/case_id'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/add_case_comment_request'
+ examples:
+ createCaseCommentRequest:
+ $ref: '#/components/examples/add_comment_request'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/case_response_properties'
+ examples:
+ createCaseCommentResponse:
+ $ref: '#/components/examples/add_comment_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ delete:
+ summary: Deletes all comments and alerts from a case in the default space.
+ operationId: deleteCaseCommentsDefaultSpace
+ description: |
+ You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/kbn_xsrf'
+ - $ref: '#/components/parameters/case_id'
+ responses:
+ '204':
+ description: Indicates a successful call.
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ patch:
+ summary: Updates a comment or alert in a case in the default space.
+ operationId: updateCaseCommentDefaultSpace
+ description: |
+ You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're updating. NOTE: You cannot change the comment type or the owner of a comment.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/kbn_xsrf'
+ - $ref: '#/components/parameters/case_id'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/update_case_comment_request'
+ examples:
+ updateCaseCommentRequest:
+ $ref: '#/components/examples/update_comment_request'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/case_response_properties'
+ examples:
+ updateCaseCommentResponse:
+ $ref: '#/components/examples/update_comment_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ get:
+ summary: Retrieves all the comments from a case in the default space.
+ operationId: getAllCaseCommentsDefaultSpace
+ description: |
+ Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; instead, use the get case comment API, which requires a comment identifier in the path. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.
+ deprecated: true
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/case_id'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/case_response_properties'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/{caseId}/comments/{commentId}:
+ delete:
+ summary: Deletes a comment or alert from a case in the default space.
+ operationId: deleteCaseCommentDefaultSpace
+ description: |
+ You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're deleting.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/kbn_xsrf'
+ - $ref: '#/components/parameters/case_id'
+ - $ref: '#/components/parameters/comment_id'
+ responses:
+ '204':
+ description: Indicates a successful call.
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ get:
+ summary: Retrieves a comment from a case in the default space.
+ operationId: getCaseCommentDefaultSpace
+ description: |
+ You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases with the comments you're seeking.
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/case_id'
+ - $ref: '#/components/parameters/comment_id'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/alert_comment_response_properties'
+ - $ref: '#/components/schemas/user_comment_response_properties'
+ examples:
+ getCaseCommentResponse:
+ $ref: '#/components/examples/get_comment_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/{caseId}/connector/{connectorId}/_push:
+ post:
+ summary: Pushes a case in the default space to an external service.
+ description: |
+ You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges. You must also have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're pushing.
+ operationId: pushCaseDefaultSpace
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/case_id'
+ - $ref: '#/components/parameters/connector_id'
+ - $ref: '#/components/parameters/kbn_xsrf'
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ nullable: true
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/case_response_properties'
+ examples:
+ pushCaseResponse:
+ $ref: '#/components/examples/push_case_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/{caseId}/user_actions:
+ get:
+ summary: Returns all user activity for a case in the default space.
+ description: |
+ Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead. You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.
+ deprecated: true
+ operationId: getCaseActivityDefaultSpace
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/case_id'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/user_actions_response_properties'
+ examples:
+ getCaseActivityResponse:
+ $ref: '#/components/examples/get_case_activity_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/{caseId}/user_actions/_find:
+ get:
+ summary: Finds user activity for a case in the default space.
+ description: |
+ You must have `read` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're seeking.
+ operationId: findCaseActivityDefaultSpace
+ tags:
+ - cases
+ parameters:
+ - $ref: '#/components/parameters/case_id'
+ - $ref: '#/components/parameters/page_index'
+ - $ref: '#/components/parameters/page_size'
+ - $ref: '#/components/parameters/sort_order'
+ - $ref: '#/components/parameters/user_action_types'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ page:
+ type: integer
+ perPage:
+ type: integer
+ total:
+ type: integer
+ userActions:
+ type: array
+ items:
+ $ref: '#/components/schemas/user_actions_find_response_properties'
+ examples:
+ findCaseActivityResponse:
+ $ref: '#/components/examples/find_case_activity_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
+ /api/cases/configure/connectors/_find:
+ get:
+ summary: Retrieves information about connectors in the default space.
+ operationId: findCaseConnectorsDefaultSpace
+ description: |
+ In particular, only the connectors that are supported for use in cases are returned. You must have `read` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.
+ tags:
+ - cases
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ actionTypeId:
+ $ref: '#/components/schemas/connector_types'
+ config:
+ type: object
+ properties:
+ apiUrl:
+ type: string
+ projectKey:
+ type: string
+ additionalProperties: true
+ id:
+ type: string
+ isDeprecated:
+ type: boolean
+ isMissingSecrets:
+ type: boolean
+ isPreconfigured:
+ type: boolean
+ name:
+ type: string
+ referencedByCount:
+ type: integer
+ examples:
+ findConnectorResponse:
+ $ref: '#/components/examples/find_connector_response'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/4xx_response'
+ servers:
+ - url: https://localhost:5601
+ servers:
+ - url: https://localhost:5601
/s/{spaceId}/api/cases:
post:
summary: Creates a case.
@@ -62,14 +1166,8 @@ paths:
- cases
parameters:
- $ref: '#/components/parameters/kbn_xsrf'
+ - $ref: '#/components/parameters/ids'
- $ref: '#/components/parameters/space_id'
- - name: ids
- description: The cases that you want to removed. All non-ASCII characters must be URL encoded.
- in: query
- required: true
- schema:
- type: string
- example: d4e7abb0-b462-11ec-9a8d-698504725a43
responses:
'204':
description: Indicates a successful call.
@@ -131,109 +1229,22 @@ paths:
- cases
parameters:
- $ref: '#/components/parameters/space_id'
- - name: assignees
- in: query
- description: |
- Filters the returned cases by assignees. Valid values are `none` or unique identifiers for the user profiles. These identifiers can be found by using the suggest user profile API.
- schema:
- oneOf:
- - type: string
- - type: array
- items:
- type: string
- maxItems: 100
- - name: category
- in: query
- description: Filters the returned cases by category. Limited to 100 categories.
- schema:
- oneOf:
- - type: string
- - type: array
- items:
- type: string
- example: my-category
- - name: defaultSearchOperator
- in: query
- description: The default operator to use for the simple_query_string.
- schema:
- type: string
- default: OR
- example: OR
- - name: from
- in: query
- description: |
- [preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.
- schema:
- type: string
- example: now-1d
+ - $ref: '#/components/parameters/assignees'
+ - $ref: '#/components/parameters/category'
+ - $ref: '#/components/parameters/defaultSearchOperator'
+ - $ref: '#/components/parameters/from'
- $ref: '#/components/parameters/owner'
- $ref: '#/components/parameters/page_index'
- $ref: '#/components/parameters/page_size'
- - name: reporters
- in: query
- description: Filters the returned cases by the user name of the reporter.
- schema:
- oneOf:
- - type: string
- - type: array
- items:
- type: string
- maxItems: 100
- example: elastic
- - name: search
- in: query
- description: An Elasticsearch simple_query_string query that filters the objects in the response.
- schema:
- type: string
- - name: searchFields
- in: query
- description: The fields to perform the simple_query_string parsed query against.
- schema:
- oneOf:
- - $ref: '#/components/schemas/search_fields'
- - type: array
- items:
- $ref: '#/components/schemas/search_fields'
+ - $ref: '#/components/parameters/reporters'
+ - $ref: '#/components/parameters/search'
+ - $ref: '#/components/parameters/searchFields'
- $ref: '#/components/parameters/severity'
- - name: sortField
- in: query
- description: Determines which field is used to sort the results.
- schema:
- type: string
- enum:
- - createdAt
- - updatedAt
- default: createdAt
- example: updatedAt
+ - $ref: '#/components/parameters/sortField'
- $ref: '#/components/parameters/sort_order'
- - name: status
- in: query
- description: Filters the returned cases by state.
- schema:
- type: string
- enum:
- - closed
- - in-progress
- - open
- example: open
- - name: tags
- in: query
- description: Filters the returned cases by tags.
- schema:
- oneOf:
- - type: string
- - type: array
- items:
- type: string
- maxItems: 100
- example: tag-1
- - name: to
- in: query
- description: |
- [preview] Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.
- schema:
- type: string
- example: now+1d
+ - $ref: '#/components/parameters/status'
+ - $ref: '#/components/parameters/tags'
+ - $ref: '#/components/parameters/to'
responses:
'200':
description: Indicates a successful call.
@@ -447,49 +1458,7 @@ paths:
content:
application/json:
schema:
- type: object
- properties:
- closure_type:
- $ref: '#/components/schemas/closure_types'
- connector:
- description: An object that contains the connector configuration.
- type: object
- properties:
- fields:
- description: The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.
- nullable: true
- type: object
- id:
- description: The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.
- type: string
- example: none
- name:
- description: The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.
- type: string
- example: none
- type:
- $ref: '#/components/schemas/connector_types'
- required:
- - fields
- - id
- - name
- - type
- owner:
- $ref: '#/components/schemas/owners'
- settings:
- description: An object that contains the case settings.
- type: object
- properties:
- syncAlerts:
- description: Turns alert syncing on or off.
- type: boolean
- example: true
- required:
- - syncAlerts
- required:
- - closure_type
- - connector
- - owner
+ $ref: '#/components/schemas/set_case_configuration_request'
examples:
setCaseConfigRequest:
$ref: '#/components/examples/set_case_configuration_request'
@@ -621,40 +1590,7 @@ paths:
content:
application/json:
schema:
- type: object
- properties:
- closure_type:
- $ref: '#/components/schemas/closure_types'
- connector:
- description: An object that contains the connector configuration.
- type: object
- properties:
- fields:
- description: The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.
- nullable: true
- type: object
- id:
- description: The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.
- type: string
- example: none
- name:
- description: The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.
- type: string
- example: none
- type:
- $ref: '#/components/schemas/connector_types'
- required:
- - fields
- - id
- - name
- - type
- version:
- description: |
- The version of the connector. To retrieve the version value, use the get configuration API.
- type: string
- example: WzIwMiwxXQ==
- required:
- - version
+ $ref: '#/components/schemas/update_case_configuration_request'
responses:
'200':
description: Indicates a successful call.
@@ -919,15 +1855,7 @@ paths:
- cases
parameters:
- $ref: '#/components/parameters/space_id'
- - in: query
- name: owner
- description: A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read.
- schema:
- oneOf:
- - $ref: '#/components/schemas/owners'
- - type: array
- items:
- $ref: '#/components/schemas/owners'
+ - $ref: '#/components/parameters/owner'
responses:
'200':
description: Indicates a successful call.
@@ -961,13 +1889,7 @@ paths:
parameters:
- $ref: '#/components/parameters/case_id'
- $ref: '#/components/parameters/space_id'
- - in: query
- name: includeComments
- description: Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned.
- deprecated: true
- schema:
- type: boolean
- default: true
+ - $ref: '#/components/parameters/includeComments'
responses:
'200':
description: Indicates a successful call.
@@ -1332,30 +2254,7 @@ paths:
example: '1'
- $ref: '#/components/parameters/page_size'
- $ref: '#/components/parameters/sort_order'
- - name: types
- in: query
- description: Determines the types of user actions to return.
- schema:
- type: array
- items:
- type: string
- enum:
- - action
- - alert
- - assignees
- - attachment
- - comment
- - connector
- - create_case
- - description
- - pushed
- - settings
- - severity
- - status
- - tags
- - title
- - user
- example: create_case
+ - $ref: '#/components/parameters/user_action_types'
responses:
'200':
description: Indicates a successful call.
@@ -1404,14 +2303,55 @@ components:
name: kbn-xsrf
description: Cross-site request forgery protection
required: true
- space_id:
- in: path
- name: spaceId
- description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.
+ ids:
+ name: ids
+ description: |
+ The cases that you want to removed. All non-ASCII characters must be URL encoded.
+ in: query
required: true
schema:
type: string
- example: default
+ example: d4e7abb0-b462-11ec-9a8d-698504725a43
+ assignees:
+ in: query
+ name: assignees
+ description: |
+ Filters the returned cases by assignees. Valid values are `none` or unique identifiers for the user profiles. These identifiers can be found by using the suggest user profile API.
+ schema:
+ oneOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+ maxItems: 100
+ category:
+ in: query
+ name: category
+ description: Filters the returned cases by category.
+ schema:
+ oneOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+ maxItems: 100
+ example: my-category
+ defaultSearchOperator:
+ in: query
+ name: defaultSearchOperator
+ description: he default operator to use for the simple_query_string.
+ schema:
+ type: string
+ default: OR
+ example: OR
+ from:
+ in: query
+ name: from
+ description: |
+ [preview] Returns only cases that were created after a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.
+ schema:
+ type: string
+ example: now-1d
owner:
in: query
name: owner
@@ -1440,6 +2380,34 @@ components:
schema:
type: integer
default: 20
+ reporters:
+ in: query
+ name: reporters
+ description: Filters the returned cases by the user name of the reporter.
+ schema:
+ oneOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+ maxItems: 100
+ example: elastic
+ search:
+ in: query
+ name: search
+ description: An Elasticsearch simple_query_string query that filters the objects in the response.
+ schema:
+ type: string
+ searchFields:
+ in: query
+ name: searchFields
+ description: The fields to perform the simple_query_string parsed query against.
+ schema:
+ oneOf:
+ - $ref: '#/components/schemas/searchFieldsType'
+ - type: array
+ items:
+ $ref: '#/components/schemas/searchFieldsType'
severity:
in: query
name: severity
@@ -1451,6 +2419,17 @@ components:
- high
- low
- medium
+ sortField:
+ in: query
+ name: sortField
+ description: Determines which field is used to sort the results.
+ schema:
+ type: string
+ enum:
+ - createdAt
+ - updatedAt
+ default: createdAt
+ example: updatedAt
sort_order:
in: query
name: sortOrder
@@ -1462,6 +2441,37 @@ components:
- asc
- desc
default: desc
+ status:
+ in: query
+ name: status
+ description: Filters the returned cases by state.
+ schema:
+ type: string
+ enum:
+ - closed
+ - in-progress
+ - open
+ example: open
+ tags:
+ in: query
+ name: tags
+ description: Filters the returned cases by tags.
+ schema:
+ oneOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+ maxItems: 100
+ example: tag-1
+ to:
+ in: query
+ name: to
+ description: |
+ [preview] Returns only cases that were created before a specific date. The date must be specified as a KQL data range or date match expression. This functionality is in technical preview and may be changed or removed in a future release. Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.
+ schema:
+ type: string
+ example: now+1d
alert_id:
in: path
name: alertId
@@ -1486,6 +2496,14 @@ components:
schema:
type: string
example: 9c235210-6834-11ea-a78c-6ffb38a34414
+ includeComments:
+ in: query
+ name: includeComments
+ description: Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned.
+ deprecated: true
+ schema:
+ type: boolean
+ default: true
comment_id:
in: path
name: commentId
@@ -1503,6 +2521,39 @@ components:
schema:
type: string
example: abed3a70-71bd-11ea-a0b2-c51ea50a58e2
+ user_action_types:
+ in: query
+ name: types
+ description: Determines the types of user actions to return.
+ schema:
+ type: array
+ items:
+ type: string
+ enum:
+ - action
+ - alert
+ - assignees
+ - attachment
+ - comment
+ - connector
+ - create_case
+ - description
+ - pushed
+ - settings
+ - severity
+ - status
+ - tags
+ - title
+ - user
+ example: create_case
+ space_id:
+ in: path
+ name: spaceId
+ description: An identifier for the space. If `/s/` and the identifier are omitted from the path, the default space is used.
+ required: true
+ schema:
+ type: string
+ example: default
schemas:
assignees:
type: array
@@ -2320,7 +3371,7 @@ components:
version:
description: The current version of the case. To determine this value, use the get case or find cases APIs.
type: string
- search_fields:
+ searchFieldsType:
type: string
description: The fields to perform the `simple_query_string` parsed query against.
enum:
@@ -2368,6 +3419,89 @@ components:
- .servicenow-sir
- .swimlane
example: .none
+ set_case_configuration_request:
+ title: Set case configuration request
+ description: External connection details, such as the closure type and default connector for cases.
+ type: object
+ required:
+ - closure_type
+ - connector
+ - owner
+ properties:
+ closure_type:
+ $ref: '#/components/schemas/closure_types'
+ connector:
+ description: An object that contains the connector configuration.
+ type: object
+ properties:
+ fields:
+ description: The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.
+ nullable: true
+ type: object
+ id:
+ description: The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.
+ type: string
+ example: none
+ name:
+ description: The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.
+ type: string
+ example: none
+ type:
+ $ref: '#/components/schemas/connector_types'
+ required:
+ - fields
+ - id
+ - name
+ - type
+ owner:
+ $ref: '#/components/schemas/owners'
+ settings:
+ description: An object that contains the case settings.
+ type: object
+ properties:
+ syncAlerts:
+ description: Turns alert syncing on or off.
+ type: boolean
+ example: true
+ required:
+ - syncAlerts
+ update_case_configuration_request:
+ title: Update case configuration request
+ description: External connection details, such as the closure type and default connector for cases.
+ type: object
+ required:
+ - version
+ properties:
+ closure_type:
+ $ref: '#/components/schemas/closure_types'
+ connector:
+ description: An object that contains the connector configuration.
+ type: object
+ properties:
+ fields:
+ description: The fields specified in the case configuration are not used and are not propagated to individual cases, therefore it is recommended to set it to `null`.
+ nullable: true
+ type: object
+ id:
+ description: The identifier for the connector. If you do not want a default connector, use `none`. To retrieve connector IDs, use the find connectors API.
+ type: string
+ example: none
+ name:
+ description: The name of the connector. If you do not want a default connector, use `none`. To retrieve connector names, use the find connectors API.
+ type: string
+ example: none
+ type:
+ $ref: '#/components/schemas/connector_types'
+ required:
+ - fields
+ - id
+ - name
+ - type
+ version:
+ description: |
+ The version of the connector. To retrieve the version value, use the get configuration API.
+ type: string
+ example: WzIwMiwxXQ==
alert_response_properties:
type: object
properties:
@@ -3202,19 +4336,6 @@ components:
version: WzIwNzMsMV0=
error: null
id: 4a97a440-e1cd-11ec-be9b-9b1838238ee6
- find_connector_response:
- summary: Retrieve information about the connectors and their settings.
- value:
- - id: 61787f53-4eee-4741-8df6-8fe84fa616f7
- actionTypeId: .jira
- name: my-Jira
- isMissingSecrets: false
- config:
- apiUrl: https://elastic.atlassian.net/
- projectKey: ES
- isPreconfigured: false
- isDeprecated: false
- referencedByCount: 0
get_reporters_response:
summary: A list of three users that opened cases
value:
@@ -3615,6 +4736,19 @@ components:
uid: u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0
version: WzM1ODg4LDFb
type: assignees
+ find_connector_response:
+ summary: Retrieve information about the connectors and their settings.
+ value:
+ - id: 61787f53-4eee-4741-8df6-8fe84fa616f7
+ actionTypeId: .jira
+ name: my-Jira
+ isMissingSecrets: false
+ config:
+ apiUrl: https://elastic.atlassian.net/
+ projectKey: ES
+ isPreconfigured: false
+ isDeprecated: false
+ referencedByCount: 0
security:
- basicAuth: []
- apiKeyAuth: []
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/assignees.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/assignees.yaml
new file mode 100644
index 0000000000000..a4c81c67f6c67
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/assignees.yaml
@@ -0,0 +1,13 @@
+in: query
+name: assignees
+description: >
+ Filters the returned cases by assignees.
+ Valid values are `none` or unique identifiers for the user profiles.
+ These identifiers can be found by using the suggest user profile API.
+schema:
+ oneOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+ maxItems: 100
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/category.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/category.yaml
new file mode 100644
index 0000000000000..8bf20d9aa2450
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/category.yaml
@@ -0,0 +1,11 @@
+in: query
+name: category
+description: Filters the returned cases by category.
+schema:
+ oneOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+ maxItems: 100
+example: my-category
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/defaultSearchOperator.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/defaultSearchOperator.yaml
new file mode 100644
index 0000000000000..8e9004c859b46
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/defaultSearchOperator.yaml
@@ -0,0 +1,7 @@
+in: query
+name: defaultSearchOperator
+description: he default operator to use for the simple_query_string.
+schema:
+ type: string
+ default: OR
+example: OR
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/from.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/from.yaml
new file mode 100644
index 0000000000000..3b20869c0da6a
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/from.yaml
@@ -0,0 +1,10 @@
+in: query
+name: from
+description: >
+ [preview] Returns only cases that were created after a specific date.
+ The date must be specified as a KQL data range or date match expression.
+ This functionality is in technical preview and may be changed or removed in a future release.
+ Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.
+schema:
+ type: string
+ example: now-1d
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/ids.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/ids.yaml
new file mode 100644
index 0000000000000..1690b5e6038a0
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/ids.yaml
@@ -0,0 +1,12 @@
+name: ids
+description: >
+ The cases that you want to removed.
+ All non-ASCII characters must be URL encoded.
+in: query
+required: true
+schema:
+ type: string
+example: d4e7abb0-b462-11ec-9a8d-698504725a43
+
+
+
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/includeComments.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/includeComments.yaml
new file mode 100644
index 0000000000000..2a2b12df1a421
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/includeComments.yaml
@@ -0,0 +1,7 @@
+in: query
+name: includeComments
+description: Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned.
+deprecated: true
+schema:
+ type: boolean
+ default: true
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/reporters.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/reporters.yaml
new file mode 100644
index 0000000000000..db28a6c48ae02
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/reporters.yaml
@@ -0,0 +1,11 @@
+in: query
+name: reporters
+description: Filters the returned cases by the user name of the reporter.
+schema:
+ oneOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+ maxItems: 100
+example: elastic
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/search.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/search.yaml
new file mode 100644
index 0000000000000..5435a43a06b93
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/search.yaml
@@ -0,0 +1,5 @@
+in: query
+name: search
+description: An Elasticsearch simple_query_string query that filters the objects in the response.
+schema:
+ type: string
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/searchFields.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/searchFields.yaml
new file mode 100644
index 0000000000000..8b06f182aade8
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/searchFields.yaml
@@ -0,0 +1,9 @@
+in: query
+name: searchFields
+description: The fields to perform the simple_query_string parsed query against.
+schema:
+ oneOf:
+ - $ref: 'searchFieldsType.yaml'
+ - type: array
+ items:
+ $ref: 'searchFieldsType.yaml'
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/search_fields.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/searchFieldsType.yaml
similarity index 100%
rename from x-pack/plugins/cases/docs/openapi/components/parameters/search_fields.yaml
rename to x-pack/plugins/cases/docs/openapi/components/parameters/searchFieldsType.yaml
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/sortField.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/sortField.yaml
new file mode 100644
index 0000000000000..c0b732f905c56
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/sortField.yaml
@@ -0,0 +1,10 @@
+in: query
+name: sortField
+description: Determines which field is used to sort the results.
+schema:
+ type: string
+ enum:
+ - createdAt
+ - updatedAt
+ default: createdAt
+example: updatedAt
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/status.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/status.yaml
new file mode 100644
index 0000000000000..0517e7516a87f
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/status.yaml
@@ -0,0 +1,10 @@
+in: query
+name: status
+description: Filters the returned cases by state.
+schema:
+ type: string
+ enum:
+ - closed
+ - in-progress
+ - open
+example: open
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/tags.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/tags.yaml
new file mode 100644
index 0000000000000..d899edbcc38eb
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/tags.yaml
@@ -0,0 +1,11 @@
+in: query
+name: tags
+description: Filters the returned cases by tags.
+schema:
+ oneOf:
+ - type: string
+ - type: array
+ items:
+ type: string
+ maxItems: 100
+example: tag-1
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/to.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/to.yaml
new file mode 100644
index 0000000000000..fb41e6b8223b9
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/to.yaml
@@ -0,0 +1,10 @@
+in: query
+name: to
+description: >
+ [preview] Returns only cases that were created before a specific date.
+ The date must be specified as a KQL data range or date match expression.
+ This functionality is in technical preview and may be changed or removed in a future release.
+ Elastic will apply best effort to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.
+schema:
+ type: string
+example: now+1d
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/components/parameters/user_action_types.yaml b/x-pack/plugins/cases/docs/openapi/components/parameters/user_action_types.yaml
new file mode 100644
index 0000000000000..2b04b7c806620
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/parameters/user_action_types.yaml
@@ -0,0 +1,24 @@
+in: query
+name: types
+description: Determines the types of user actions to return.
+schema:
+ type: array
+ items:
+ type: string
+ enum:
+ - action
+ - alert
+ - assignees
+ - attachment
+ - comment
+ - connector
+ - create_case
+ - description
+ - pushed
+ - settings
+ - severity
+ - status
+ - tags
+ - title
+ - user
+example: create_case
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/set_case_configuration_request.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/set_case_configuration_request.yaml
new file mode 100644
index 0000000000000..44a3a8709a939
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/schemas/set_case_configuration_request.yaml
@@ -0,0 +1,32 @@
+title: Set case configuration request
+description: External connection details, such as the closure type and default connector for cases.
+type: object
+required:
+ - closure_type
+ - connector
+ - owner
+properties:
+ closure_type:
+ $ref: 'closure_types.yaml'
+ connector:
+ description: An object that contains the connector configuration.
+ type: object
+ properties:
+ $ref: 'case_configure_connector_properties.yaml'
+ required:
+ - fields
+ - id
+ - name
+ - type
+ owner:
+ $ref: 'owners.yaml'
+ settings:
+ description: An object that contains the case settings.
+ type: object
+ properties:
+ syncAlerts:
+ description: Turns alert syncing on or off.
+ type: boolean
+ example: true
+ required:
+ - syncAlerts
diff --git a/x-pack/plugins/cases/docs/openapi/components/schemas/update_case_configuration_request.yaml b/x-pack/plugins/cases/docs/openapi/components/schemas/update_case_configuration_request.yaml
new file mode 100644
index 0000000000000..89c03715944c4
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/components/schemas/update_case_configuration_request.yaml
@@ -0,0 +1,24 @@
+title: Update case configuration request
+description: External connection details, such as the closure type and default connector for cases.
+type: object
+required:
+ - version
+properties:
+ closure_type:
+ $ref: 'closure_types.yaml'
+ connector:
+ description: An object that contains the connector configuration.
+ type: object
+ properties:
+ $ref: 'case_configure_connector_properties.yaml'
+ required:
+ - fields
+ - id
+ - name
+ - type
+ version:
+ description: >
+ The version of the connector.
+ To retrieve the version value, use the get configuration API.
+ type: string
+ example: WzIwMiwxXQ==
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml
index a4b09e63ee2d4..142dcd2706fd3 100644
--- a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml
+++ b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml
@@ -15,6 +15,40 @@ servers:
- url: 'http://localhost:5601'
description: local
paths:
+# Paths in the default space
+ '/api/cases':
+ $ref: 'paths/api@cases.yaml'
+ '/api/cases/_find':
+ $ref: 'paths/api@cases@_find.yaml'
+ '/api/cases/alerts/{alertId}':
+ $ref: 'paths/api@cases@alerts@{alertid}.yaml'
+ '/api/cases/configure':
+ $ref: paths/api@cases@configure.yaml
+ '/api/cases/configure/{configurationId}':
+ $ref: paths/api@cases@configure@{configurationid}.yaml
+ '/api/cases/reporters':
+ $ref: 'paths/api@cases@reporters.yaml'
+ '/api/cases/status':
+ $ref: 'paths/api@cases@status.yaml'
+ '/api/cases/tags':
+ $ref: 'paths/api@cases@tags.yaml'
+ '/api/cases/{caseId}':
+ $ref: 'paths/api@cases@{caseid}.yaml'
+ '/api/cases/{caseId}/alerts':
+ $ref: 'paths/api@cases@{caseid}@alerts.yaml'
+ '/api/cases/{caseId}/comments':
+ $ref: 'paths/api@cases@{caseid}@comments.yaml'
+ '/api/cases/{caseId}/comments/{commentId}':
+ $ref: 'paths/api@cases@{caseid}@comments@{commentid}.yaml'
+ '/api/cases/{caseId}/connector/{connectorId}/_push':
+ $ref: 'paths/api@cases@{caseid}@connector@{connectorid}@_push.yaml'
+ '/api/cases/{caseId}/user_actions':
+ $ref: 'paths/api@cases@{caseid}@user_actions.yaml'
+ '/api/cases/{caseId}/user_actions/_find':
+ $ref: 'paths/api@cases@{caseid}@user_actions@_find.yaml'
+ '/api/cases/configure/connectors/_find':
+ $ref: paths/api@cases@configure@connectors@_find.yaml
+# Paths with space identifiers
'/s/{spaceId}/api/cases':
$ref: 'paths/s@{spaceid}@api@cases.yaml'
'/s/{spaceId}/api/cases/_find':
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases.yaml
new file mode 100644
index 0000000000000..98e97b6af7d5b
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases.yaml
@@ -0,0 +1,106 @@
+post:
+ summary: Creates a case in the default space.
+ operationId: createCaseDefaultSpace
+ description: >
+ You must have `all` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the case you're creating.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/headers/kbn_xsrf.yaml'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/create_case_request.yaml'
+ examples:
+ createCaseRequest:
+ $ref: '../components/examples/create_case_request.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/case_response_properties.yaml'
+ examples:
+ createCaseResponse:
+ $ref: '../components/examples/create_case_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+
+delete:
+ summary: Deletes one or more cases in the default space.
+ operationId: deleteCaseDefaultSpace
+ description: >
+ You must have `read` or `all` privileges and the `delete` sub-feature
+ privilege for the **Cases** feature in the **Management**,
+ **Observability**, or **Security** section of the Kibana feature privileges,
+ depending on the owner of the cases you're deleting.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/headers/kbn_xsrf.yaml'
+ - $ref: '../components/parameters/ids.yaml'
+ responses:
+ '204':
+ description: Indicates a successful call.
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+
+patch:
+ summary: Updates one or more cases in the default space.
+ operationId: updateCaseDefaultSpace
+ description: >
+ You must have `all` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the case you're updating.
+ tags:
+ - cases
+ parameters:
+ - $ref: ../components/headers/kbn_xsrf.yaml
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/update_case_request.yaml'
+ examples:
+ updateCaseRequest:
+ $ref: '../components/examples/update_case_request.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '../components/schemas/case_response_properties.yaml'
+ examples:
+ updateCaseResponse:
+ $ref: '../components/examples/update_case_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+
+servers:
+ - url: https://localhost:5601
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@_find.yaml
new file mode 100644
index 0000000000000..2dc92988f9d32
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@_find.yaml
@@ -0,0 +1,63 @@
+get:
+ summary: Retrieves a paginated subset of cases in the default space.
+ operationId: findCasesDefaultSpace
+ description: >
+ You must have `read` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the cases you're seeking.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/assignees.yaml'
+ - $ref: '../components/parameters/category.yaml'
+ - $ref: '../components/parameters/defaultSearchOperator.yaml'
+ - $ref: '../components/parameters/from.yaml'
+ - $ref: '../components/parameters/owner.yaml'
+ - $ref: '../components/parameters/page_index.yaml'
+ - $ref: '../components/parameters/page_size.yaml'
+ - $ref: '../components/parameters/reporters.yaml'
+ - $ref: '../components/parameters/search.yaml'
+ - $ref: '../components/parameters/searchFields.yaml'
+ - $ref: '../components/parameters/severity.yaml'
+ - $ref: '../components/parameters/sortField.yaml'
+ - $ref: '../components/parameters/sort_order.yaml'
+ - $ref: '../components/parameters/status.yaml'
+ - $ref: '../components/parameters/tags.yaml'
+ - $ref: '../components/parameters/to.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ cases:
+ type: array
+ items:
+ $ref: '../components/schemas/case_response_properties.yaml'
+ count_closed_cases:
+ type: integer
+ count_in_progress_cases:
+ type: integer
+ count_open_cases:
+ type: integer
+ page:
+ type: integer
+ per_page:
+ type: integer
+ total:
+ type: integer
+ examples:
+ findCaseResponse:
+ $ref: '../components/examples/find_case_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@alerts@{alertid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@alerts@{alertid}.yaml
new file mode 100644
index 0000000000000..8ce27543da072
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@alerts@{alertid}.yaml
@@ -0,0 +1,42 @@
+get:
+ summary: Returns the cases associated with a specific alert in the default space.
+ operationId: getCasesByAlertDefaultSpace
+ description: >
+ You must have `read` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the cases you're seeking.
+ x-technical-preview: true
+ tags:
+ - cases
+ parameters:
+ - $ref: ../components/parameters/alert_id.yaml
+ - $ref: '../components/parameters/owner.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ description: The case identifier.
+ title:
+ type: string
+ description: The case title.
+ example:
+ - id: 06116b80-e1c3-11ec-be9b-9b1838238ee6
+ title: security_case
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601s
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure.yaml
new file mode 100644
index 0000000000000..ebbae5e333103
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure.yaml
@@ -0,0 +1,74 @@
+get:
+ summary: Retrieves external connection details, such as the closure type and default connector for cases in the default space.
+ operationId: getCaseConfigurationDefaultSpace
+ description: >
+ You must have `read` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the case configuration.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/owner.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ $ref: '../components/schemas/case_configure_response_properties.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+
+post:
+ summary: Sets external connection details, such as the closure type and default connector for cases in the default space.
+ operationId: setCaseConfigurationDefaultSpace
+ description: >
+ You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration.
+ Connectors are used to interface with external systems.
+ You must create a connector before you can use it in your cases. Refer to the add connectors API.
+ If you set a default connector, it is automatically selected when you create cases in Kibana.
+ If you use the create case API, however, you must still specify all of the connector details.
+ tags:
+ - cases
+ parameters:
+ - $ref: ../components/headers/kbn_xsrf.yaml
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/set_case_configuration_request.yaml'
+ examples:
+ setCaseConfigRequest:
+ $ref: '../components/examples/set_case_configuration_request.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ $ref: '../components/schemas/case_configure_response_properties.yaml'
+ examples:
+ setCaseConfigResponse:
+ $ref: '../components/examples/set_case_configuration_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@connectors@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@connectors@_find.yaml
new file mode 100644
index 0000000000000..b3618e3537819
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@connectors@_find.yaml
@@ -0,0 +1,33 @@
+get:
+ summary: Retrieves information about connectors in the default space.
+ operationId: findCaseConnectorsDefaultSpace
+ description: >
+ In particular, only the connectors that are supported for use in cases are
+ returned. You must have `read` privileges for the **Actions and Connectors**
+ feature in the **Management** section of the Kibana feature privileges.
+ tags:
+ - cases
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ $ref: '../components/schemas/connector_response_properties.yaml'
+ examples:
+ findConnectorResponse:
+ $ref: '../components/examples/find_connector_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@{configurationid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@{configurationid}.yaml
new file mode 100644
index 0000000000000..507842e6f47f4
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@configure@{configurationid}.yaml
@@ -0,0 +1,39 @@
+patch:
+ summary: Updates external connection details, such as the closure type and default connector for cases in the default space.
+ operationId: updateCaseConfigurationDefaultSpace
+ description: >
+ You must have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case configuration.
+ Connectors are used to interface with external systems.
+ You must create a connector before you can use it in your cases.
+ Refer to the add connectors API.
+ tags:
+ - cases
+ parameters:
+ - $ref: ../components/headers/kbn_xsrf.yaml
+ - $ref: ../components/parameters/configuration_id.yaml
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/update_case_configuration_request.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ $ref: '../components/schemas/case_configure_response_properties.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@reporters.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@reporters.yaml
new file mode 100644
index 0000000000000..2410b238ed284
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@reporters.yaml
@@ -0,0 +1,35 @@
+get:
+ summary: Returns information about the users who opened cases in the default space.
+ operationId: getCaseReportersDefaultSpace
+ description: >
+ You must have read privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases.
+ The API returns information about the users as they existed at the time of the case creation, including their name, full name, and email address.
+ If any of those details change thereafter or if a user is deleted, the information returned by this API is unchanged.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/owner.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ $ref: '../components/schemas/user_properties.yaml'
+ examples:
+ getReportersResponse:
+ $ref: '../components/examples/get_reporters_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@status.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@status.yaml
new file mode 100644
index 0000000000000..17dedefd16450
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@status.yaml
@@ -0,0 +1,40 @@
+get:
+ summary: Returns the number of cases that are open, closed, and in progress in the default space.
+ operationId: getCaseStatusDefaultSpace
+ description: >
+ Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find cases API instead.
+ You must have `read` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the cases you're seeking.
+ deprecated: true
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/owner.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ count_closed_cases:
+ type: integer
+ count_in_progress_cases:
+ type: integer
+ count_open_cases:
+ type: integer
+ examples:
+ getStatusResponse:
+ $ref: '../components/examples/get_status_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@tags.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@tags.yaml
new file mode 100644
index 0000000000000..e456816163baf
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@tags.yaml
@@ -0,0 +1,31 @@
+get:
+ summary: Aggregates and returns a list of case tags in the default space.
+ operationId: getCaseTagsDefaultSpace
+ description: >
+ You must have read privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/owner.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: string
+ examples:
+ getTagsResponse:
+ $ref: '../components/examples/get_tags_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml
new file mode 100644
index 0000000000000..48238dcdcf4de
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}.yaml
@@ -0,0 +1,32 @@
+get:
+ summary: Retrieves information about a case in the default space.
+ operationId: getCaseDefaultSpace
+ description: >
+ You must have `read` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the case you're seeking.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/case_id.yaml'
+ - $ref: '../components/parameters/includeComments.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/case_response_properties.yaml'
+ examples:
+ getCaseResponse:
+ $ref: '../components/examples/get_case_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@alerts.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@alerts.yaml
new file mode 100644
index 0000000000000..80e4093303136
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@alerts.yaml
@@ -0,0 +1,34 @@
+get:
+ summary: Gets all alerts attached to a case in the default space.
+ description: >
+ You must have `read` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the cases you're seeking.
+ x-technical-preview: true
+ operationId: getCaseAlertsDefaultSpace
+ tags:
+ - cases
+ parameters:
+ - $ref: ../components/parameters/case_id.yaml
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '../components/schemas/alert_response_properties.yaml'
+ examples:
+ getCaseAlertsResponse:
+ $ref: '../components/examples/get_case_alerts_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments.yaml
new file mode 100644
index 0000000000000..160a5a5f221ca
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments.yaml
@@ -0,0 +1,138 @@
+post:
+ summary: Adds a comment or alert to a case in the default space.
+ operationId: addCaseCommentDefaultSpace
+ description: >
+ You must have `all` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the case you're creating.
+ NOTE: Each case can have a maximum of 1,000 alerts.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/headers/kbn_xsrf.yaml'
+ - $ref: '../components/parameters/case_id.yaml'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/add_case_comment_request.yaml'
+ examples:
+ createCaseCommentRequest:
+ $ref: '../components/examples/add_comment_request.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/case_response_properties.yaml'
+ examples:
+ createCaseCommentResponse:
+ $ref: '../components/examples/add_comment_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+
+delete:
+ summary: Deletes all comments and alerts from a case in the default space.
+ operationId: deleteCaseCommentsDefaultSpace
+ description: >
+ You must have `all` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the cases you're deleting.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/headers/kbn_xsrf.yaml'
+ - $ref: '../components/parameters/case_id.yaml'
+ responses:
+ '204':
+ description: Indicates a successful call.
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+
+patch:
+ summary: Updates a comment or alert in a case in the default space.
+ operationId: updateCaseCommentDefaultSpace
+ description: >
+ You must have `all` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the case you're updating.
+ NOTE: You cannot change the comment type or the owner of a comment.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/headers/kbn_xsrf.yaml'
+ - $ref: '../components/parameters/case_id.yaml'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/update_case_comment_request.yaml'
+ examples:
+ updateCaseCommentRequest:
+ $ref: '../components/examples/update_comment_request.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/case_response_properties.yaml'
+ examples:
+ updateCaseCommentResponse:
+ $ref: '../components/examples/update_comment_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+
+get:
+ summary: Retrieves all the comments from a case in the default space.
+ operationId: getAllCaseCommentsDefaultSpace
+ description: >
+ Deprecated in 8.1.0. This API is deprecated and will be removed in a future release;
+ instead, use the get case comment API, which requires a comment identifier in the path.
+ You must have `read` privileges for the **Cases** feature in the **Management**,
+ **Observability**, or **Security** section of the Kibana feature privileges,
+ depending on the owner of the cases with the comments you're seeking.
+ deprecated: true
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/case_id.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/case_response_properties.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+
+servers:
+ - url: https://localhost:5601
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments@{commentid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments@{commentid}.yaml
new file mode 100644
index 0000000000000..0eb35a7bb1c9a
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@comments@{commentid}.yaml
@@ -0,0 +1,59 @@
+delete:
+ summary: Deletes a comment or alert from a case in the default space.
+ operationId: deleteCaseCommentDefaultSpace
+ description: >
+ You must have `all` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the cases you're deleting.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/headers/kbn_xsrf.yaml'
+ - $ref: '../components/parameters/case_id.yaml'
+ - $ref: '../components/parameters/comment_id.yaml'
+ responses:
+ '204':
+ description: Indicates a successful call.
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+
+get:
+ summary: Retrieves a comment from a case in the default space.
+ operationId: getCaseCommentDefaultSpace
+ description: >
+ You must have `read` privileges for the **Cases** feature in the **Management**,
+ **Observability**, or **Security** section of the Kibana feature privileges,
+ depending on the owner of the cases with the comments you're seeking.
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/case_id.yaml'
+ - $ref: '../components/parameters/comment_id.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ oneOf:
+ - $ref: '../components/schemas/alert_comment_response_properties.yaml'
+ - $ref: '../components/schemas/user_comment_response_properties.yaml'
+ examples:
+ getCaseCommentResponse:
+ $ref: '../components/examples/get_comment_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@connector@{connectorid}@_push.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@connector@{connectorid}@_push.yaml
new file mode 100644
index 0000000000000..4731c69bab76a
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@connector@{connectorid}@_push.yaml
@@ -0,0 +1,38 @@
+post:
+ summary: Pushes a case in the default space to an external service.
+ description: >
+ You must have `all` privileges for the **Actions and Connectors** feature in the **Management** section of the Kibana feature privileges.
+ You must also have `all` privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the case you're pushing.
+ operationId: pushCaseDefaultSpace
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/case_id.yaml'
+ - $ref: '../components/parameters/connector_id.yaml'
+ - $ref: '../components/headers/kbn_xsrf.yaml'
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ nullable: true
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/case_response_properties.yaml'
+ examples:
+ pushCaseResponse:
+ $ref: '../components/examples/push_case_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
\ No newline at end of file
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@user_actions.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@user_actions.yaml
new file mode 100644
index 0000000000000..b0e38a119fd34
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@user_actions.yaml
@@ -0,0 +1,35 @@
+get:
+ summary: Returns all user activity for a case in the default space.
+ description: >
+ Deprecated in 8.1.0. This API is deprecated and will be removed in a future release; use the find user actions API instead.
+ You must have `read` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the case you're seeking.
+ deprecated: true
+ operationId: getCaseActivityDefaultSpace
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/case_id.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '../components/schemas/user_actions_response_properties.yaml'
+ examples:
+ getCaseActivityResponse:
+ $ref: '../components/examples/get_case_activity_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@user_actions@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@user_actions@_find.yaml
new file mode 100644
index 0000000000000..dea4c6facfd64
--- /dev/null
+++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@{caseid}@user_actions@_find.yaml
@@ -0,0 +1,46 @@
+get:
+ summary: Finds user activity for a case in the default space.
+ description: >
+ You must have `read` privileges for the **Cases** feature in the
+ **Management**, **Observability**, or **Security** section of the Kibana
+ feature privileges, depending on the owner of the case you're seeking.
+ operationId: findCaseActivityDefaultSpace
+ tags:
+ - cases
+ parameters:
+ - $ref: '../components/parameters/case_id.yaml'
+ - $ref: '../components/parameters/page_index.yaml'
+ - $ref: '../components/parameters/page_size.yaml'
+ - $ref: '../components/parameters/sort_order.yaml'
+ - $ref: '../components/parameters/user_action_types.yaml'
+ responses:
+ '200':
+ description: Indicates a successful call.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ page:
+ type: integer
+ perPage:
+ type: integer
+ total:
+ type: integer
+ userActions:
+ type: array
+ items:
+ $ref: '../components/schemas/user_actions_find_response_properties.yaml'
+ examples:
+ findCaseActivityResponse:
+ $ref: '../components/examples/find_case_activity_response.yaml'
+ '401':
+ description: Authorization information is missing or invalid.
+ content:
+ application/json:
+ schema:
+ $ref: '../components/schemas/4xx_response.yaml'
+ servers:
+ - url: https://localhost:5601
+servers:
+ - url: https://localhost:5601
diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml
index ba4b852d96405..ae228f7c1f770 100644
--- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml
+++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases.yaml
@@ -50,14 +50,8 @@ delete:
- cases
parameters:
- $ref: ../components/headers/kbn_xsrf.yaml
+ - $ref: '../components/parameters/ids.yaml'
- $ref: '../components/parameters/space_id.yaml'
- - name: ids
- description: The cases that you want to removed. All non-ASCII characters must be URL encoded.
- in: query
- required: true
- schema:
- type: string
- example: d4e7abb0-b462-11ec-9a8d-698504725a43
responses:
'204':
description: Indicates a successful call.
diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml
index d02399d081f33..fa9687b439f84 100644
--- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml
+++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@_find.yaml
@@ -9,120 +9,22 @@ get:
- cases
parameters:
- $ref: '../components/parameters/space_id.yaml'
- - name: assignees
- in: query
- description: >
- Filters the returned cases by assignees. Valid values are `none` or unique identifiers for the user profiles.
- These identifiers can be found by using the suggest user profile API.
- schema:
- oneOf:
- - type: string
- - type: array
- items:
- type: string
- maxItems: 100
- - name: category
- in: query
- description: Filters the returned cases by category. Limited to 100 categories.
- schema:
- oneOf:
- - type: string
- - type: array
- items:
- type: string
- example: my-category
- - name: defaultSearchOperator
- in: query
- description: The default operator to use for the simple_query_string.
- schema:
- type: string
- default: OR
- example: OR
- - name: from
- in: query
- description: >
- [preview] Returns only cases that were created after a specific date.
- The date must be specified as a KQL data range or date match expression.
- This functionality is in technical preview and may be changed or removed
- in a future release. Elastic will apply best effort to fix any issues,
- but features in technical preview are not subject to the support SLA of
- official GA features.
- schema:
- type: string
- example: now-1d
+ - $ref: '../components/parameters/assignees.yaml'
+ - $ref: '../components/parameters/category.yaml'
+ - $ref: '../components/parameters/defaultSearchOperator.yaml'
+ - $ref: '../components/parameters/from.yaml'
- $ref: '../components/parameters/owner.yaml'
- $ref: '../components/parameters/page_index.yaml'
- $ref: '../components/parameters/page_size.yaml'
- - name: reporters
- in: query
- description: Filters the returned cases by the user name of the reporter.
- schema:
- oneOf:
- - type: string
- - type: array
- items:
- type: string
- maxItems: 100
- example: elastic
- - name: search
- in: query
- description: An Elasticsearch simple_query_string query that filters the objects in the response.
- schema:
- type: string
- - name: searchFields
- in: query
- description: The fields to perform the simple_query_string parsed query against.
- schema:
- oneOf:
- - $ref: '../components/parameters/search_fields.yaml'
- - type: array
- items:
- $ref: '../components/parameters/search_fields.yaml'
+ - $ref: '../components/parameters/reporters.yaml'
+ - $ref: '../components/parameters/search.yaml'
+ - $ref: '../components/parameters/searchFields.yaml'
- $ref: '../components/parameters/severity.yaml'
- - name: sortField
- in: query
- description: Determines which field is used to sort the results.
- schema:
- type: string
- enum:
- - createdAt
- - updatedAt
- default: createdAt
- example: updatedAt
+ - $ref: '../components/parameters/sortField.yaml'
- $ref: '../components/parameters/sort_order.yaml'
- - name: status
- in: query
- description: Filters the returned cases by state.
- schema:
- type: string
- enum:
- - closed
- - in-progress
- - open
- example: open
- - name: tags
- in: query
- description: Filters the returned cases by tags.
- schema:
- oneOf:
- - type: string
- - type: array
- items:
- type: string
- maxItems: 100
- example: tag-1
- - name: to
- in: query
- description: >
- [preview] Returns only cases that were created before a specific date.
- The date must be specified as a KQL data range or date match expression.
- This functionality is in technical preview and may be changed or removed
- in a future release. Elastic will apply best effort to fix any issues,
- but features in technical preview are not subject to the support SLA of
- official GA features.
- schema:
- type: string
- example: now+1d
+ - $ref: '../components/parameters/status.yaml'
+ - $ref: '../components/parameters/tags.yaml'
+ - $ref: '../components/parameters/to.yaml'
responses:
'200':
description: Indicates a successful call.
diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure.yaml
index cc17f044d437c..c991005af9d70 100644
--- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure.yaml
+++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure.yaml
@@ -51,36 +51,7 @@ post:
content:
application/json:
schema:
- type: object
- properties:
- closure_type:
- $ref: '../components/schemas/closure_types.yaml'
- connector:
- description: An object that contains the connector configuration.
- type: object
- properties:
- $ref: '../components/schemas/case_configure_connector_properties.yaml'
- required:
- - fields
- - id
- - name
- - type
- owner:
- $ref: '../components/schemas/owners.yaml'
- settings:
- description: An object that contains the case settings.
- type: object
- properties:
- syncAlerts:
- description: Turns alert syncing on or off.
- type: boolean
- example: true
- required:
- - syncAlerts
- required:
- - closure_type
- - connector
- - owner
+ $ref: '../components/schemas/set_case_configuration_request.yaml'
examples:
setCaseConfigRequest:
$ref: '../components/examples/set_case_configuration_request.yaml'
diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml
index 5f8dae09abf55..749f1ece724b2 100644
--- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml
+++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@configure@{configurationid}.yaml
@@ -17,28 +17,7 @@ patch:
content:
application/json:
schema:
- type: object
- properties:
- closure_type:
- $ref: '../components/schemas/closure_types.yaml'
- connector:
- description: An object that contains the connector configuration.
- type: object
- properties:
- $ref: '../components/schemas/case_configure_connector_properties.yaml'
- required:
- - fields
- - id
- - name
- - type
- version:
- description: >
- The version of the connector. To retrieve the version value, use
- the get configuration API.
- type: string
- example: WzIwMiwxXQ==
- required:
- - version
+ $ref: '../components/schemas/update_case_configuration_request.yaml'
responses:
'200':
description: Indicates a successful call.
diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml
index 5e70cff6640e1..ff448041dd712 100644
--- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml
+++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml
@@ -9,15 +9,7 @@ get:
- cases
parameters:
- $ref: '../components/parameters/space_id.yaml'
- - in: query
- name: owner
- description: A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read.
- schema:
- oneOf:
- - $ref: '../components/schemas/owners.yaml'
- - type: array
- items:
- $ref: '../components/schemas/owners.yaml'
+ - $ref: '../components/parameters/owner.yaml'
responses:
'200':
description: Indicates a successful call.
diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml
index 856a5dc24096f..fb4f735f322eb 100644
--- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml
+++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}.yaml
@@ -8,15 +8,9 @@ get:
tags:
- cases
parameters:
- - $ref: ../components/parameters/case_id.yaml
+ - $ref: '../components/parameters/case_id.yaml'
- $ref: '../components/parameters/space_id.yaml'
- - in: query
- name: includeComments
- description: Deprecated in 8.1.0. This parameter is deprecated and will be removed in a future release. It determines whether case comments are returned.
- deprecated: true
- schema:
- type: boolean
- default: true
+ - $ref: '../components/parameters/includeComments.yaml'
responses:
'200':
description: Indicates a successful call.
diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions@_find.yaml
index 58d8d33e64118..61a1290c7c5a6 100644
--- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions@_find.yaml
+++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions@_find.yaml
@@ -14,30 +14,7 @@ get:
example: "1"
- $ref: '../components/parameters/page_size.yaml'
- $ref: '../components/parameters/sort_order.yaml'
- - name: types
- in: query
- description: Determines the types of user actions to return.
- schema:
- type: array
- items:
- type: string
- enum:
- - action
- - alert
- - assignees
- - attachment
- - comment
- - connector
- - create_case
- - description
- - pushed
- - settings
- - severity
- - status
- - tags
- - title
- - user
- example: create_case
+ - $ref: '../components/parameters/user_action_types.yaml'
responses:
'200':
description: Indicates a successful call.
diff --git a/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts
index 2826f47b3213b..3737b26cf11c1 100644
--- a/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts
+++ b/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts
@@ -5,17 +5,13 @@
* 2.0.
*/
-import type {
- ConfigurationPatchRequest,
- ConfigurationRequest,
- ActionConnector,
- ActionTypeConnector,
-} from '../../../../common/api';
+import type { ActionConnector, ActionTypeConnector } from '../../../../common/api';
import type { ApiProps } from '../../types';
import type { CaseConfigure } from '../types';
import { caseConfigurationCamelCaseResponseMock } from '../mock';
import { actionTypesMock, connectorsMock } from '../../../common/mock/connectors';
+import type { ConfigurationPatchRequest, ConfigurationRequest } from '../../../../common/types/api';
export const getSupportedActionConnectors = async ({
signal,
diff --git a/x-pack/plugins/cases/public/containers/configure/api.ts b/x-pack/plugins/cases/public/containers/configure/api.ts
index 3405bbabf64b8..7ca6481a417cf 100644
--- a/x-pack/plugins/cases/public/containers/configure/api.ts
+++ b/x-pack/plugins/cases/public/containers/configure/api.ts
@@ -7,15 +7,10 @@
import { isEmpty } from 'lodash/fp';
import { CasesConnectorFeatureId } from '@kbn/actions-plugin/common';
+import type { ConfigurationPatchRequest, ConfigurationRequest } from '../../../common/types/api';
+import type { Configuration, Configurations } from '../../../common/types/domain';
import { getAllConnectorTypesUrl } from '../../../common/utils/connectors_api';
-import type {
- ActionConnector,
- ActionTypeConnector,
- ConfigurationPatchRequest,
- ConfigurationRequest,
- Configuration,
- Configurations,
-} from '../../../common/api';
+import type { ActionConnector, ActionTypeConnector } from '../../../common/api';
import { getCaseConfigurationDetailsUrl } from '../../../common/api';
import { CASE_CONFIGURE_CONNECTORS_URL, CASE_CONFIGURE_URL } from '../../../common/constants';
import { KibanaServices } from '../../common/lib/kibana';
diff --git a/x-pack/plugins/cases/public/containers/configure/mock.ts b/x-pack/plugins/cases/public/containers/configure/mock.ts
index c350b70c58632..51bdb2117f4de 100644
--- a/x-pack/plugins/cases/public/containers/configure/mock.ts
+++ b/x-pack/plugins/cases/public/containers/configure/mock.ts
@@ -5,7 +5,8 @@
* 2.0.
*/
-import type { Configuration, ConfigurationRequest } from '../../../common/api';
+import type { ConfigurationRequest } from '../../../common/types/api';
+import type { Configuration } from '../../../common/types/domain';
import { ConnectorTypes } from '../../../common/api';
import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import type { CaseConfigure, CaseConnectorMapping } from './types';
diff --git a/x-pack/plugins/cases/public/containers/configure/types.ts b/x-pack/plugins/cases/public/containers/configure/types.ts
index 1dec01523d0f7..ca0a6d14bea91 100644
--- a/x-pack/plugins/cases/public/containers/configure/types.ts
+++ b/x-pack/plugins/cases/public/containers/configure/types.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import type { ClosureType, ConfigurationAttributes } from '../../../common/types/domain';
import type { CaseUser } from '../types';
import type {
ActionConnector,
@@ -12,9 +13,7 @@ import type {
ActionType,
CaseConnector,
CaseField,
- ClosureType,
ThirdPartyField,
- ConfigurationAttributes,
} from '../../../common/api';
export type {
diff --git a/x-pack/plugins/cases/public/containers/utils.ts b/x-pack/plugins/cases/public/containers/utils.ts
index 2d59f38149506..cbeaff9b2c873 100644
--- a/x-pack/plugins/cases/public/containers/utils.ts
+++ b/x-pack/plugins/cases/public/containers/utils.ts
@@ -11,10 +11,10 @@ import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import type { ToastInputFields } from '@kbn/core/public';
+import type { Configuration, Configurations } from '../../common/types/domain';
+import { ConfigurationRt, ConfigurationsRt } from '../../common/types/domain';
import { NO_ASSIGNEES_FILTERING_KEYWORD } from '../../common/constants';
import type {
- Configurations,
- Configuration,
UserActions,
CasePatchRequest,
CaseResolveResponse,
@@ -28,8 +28,6 @@ import {
CaseRt,
CasesRt,
throwErrors,
- ConfigurationsRt,
- ConfigurationRt,
UserActionsRt,
CommentType,
CaseResolveResponseRt,
diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts
index 07b14a14a9d58..03d7c09024e71 100644
--- a/x-pack/plugins/cases/server/client/cases/push.ts
+++ b/x-pack/plugins/cases/server/client/cases/push.ts
@@ -12,11 +12,11 @@ import type { SavedObjectsFindResponse } from '@kbn/core/server';
import type { UserProfile } from '@kbn/security-plugin/common';
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
import { asSavedObjectExecutionSource } from '@kbn/actions-plugin/server';
+import type { ConfigurationAttributes } from '../../../common/types/domain';
import type {
ActionConnector,
Case,
ExternalServiceResponse,
- ConfigurationAttributes,
CommentRequestAlertType,
CommentAttributes,
} from '../../../common/api';
diff --git a/x-pack/plugins/cases/server/client/configure/client.ts b/x-pack/plugins/cases/server/client/configure/client.ts
index 0cc69b42608e4..b645fae2dbac9 100644
--- a/x-pack/plugins/cases/server/client/configure/client.ts
+++ b/x-pack/plugins/cases/server/client/configure/client.ts
@@ -14,24 +14,22 @@ import type { FindActionResult } from '@kbn/actions-plugin/server/types';
import type { ActionType } from '@kbn/actions-plugin/common';
import { CasesConnectorFeatureId } from '@kbn/actions-plugin/common';
import type {
- Configurations,
+ Configuration,
ConfigurationAttributes,
+ Configurations,
+} from '../../../common/types/domain';
+import type {
ConfigurationPatchRequest,
ConfigurationRequest,
- Configuration,
- ConnectorMappings,
GetConfigurationFindRequest,
- ConnectorMappingResponse,
-} from '../../../common/api';
+} from '../../../common/types/api';
import {
- ConfigurationsRt,
ConfigurationPatchRequestRt,
- GetConfigurationFindRequestRt,
- ConfigurationRt,
- FindActionConnectorResponseRt,
- decodeWithExcessOrThrow,
ConfigurationRequestRt,
-} from '../../../common/api';
+ GetConfigurationFindRequestRt,
+} from '../../../common/types/api';
+import type { ConnectorMappings, ConnectorMappingResponse } from '../../../common/api';
+import { FindActionConnectorResponseRt, decodeWithExcessOrThrow } from '../../../common/api';
import { MAX_CONCURRENT_SEARCHES } from '../../../common/constants';
import { createCaseError } from '../../common/error';
import type { CasesClientInternal } from '../client_internal';
@@ -44,6 +42,7 @@ import type { MappingsArgs, CreateMappingsArgs, UpdateMappingsArgs } from './typ
import { createMappings } from './create_mappings';
import { updateMappings } from './update_mappings';
import { decodeOrThrow } from '../../../common/api/runtime_types';
+import { ConfigurationRt, ConfigurationsRt } from '../../../common/types/domain';
/**
* Defines the internal helper functions.
@@ -63,7 +62,7 @@ export interface ConfigureSubClient {
/**
* Retrieves the external connector configuration for a particular case owner.
*/
- get(params: GetConfigurationFindRequest): Promise;
+ get(params: GetConfigurationFindRequest): Promise;
/**
* Retrieves the valid external connectors supported by the cases plugin.
*/
diff --git a/x-pack/plugins/cases/server/common/types/configure.ts b/x-pack/plugins/cases/server/common/types/configure.ts
index ae1b5fc0764f3..7f615a9fd5e09 100644
--- a/x-pack/plugins/cases/server/common/types/configure.ts
+++ b/x-pack/plugins/cases/server/common/types/configure.ts
@@ -8,12 +8,12 @@
import * as rt from 'io-ts';
import type { SavedObject } from '@kbn/core/server';
-import type { ConfigurationAttributes } from '../../../common/api';
+import type { ConfigurationAttributes } from '../../../common/types/domain';
import {
ConfigurationActivityFieldsRt,
- ConfigurationBasicWithoutOwnerRt,
ConfigurationAttributesRt,
-} from '../../../common/api';
+ ConfigurationBasicWithoutOwnerRt,
+} from '../../../common/types/domain';
import type { ConnectorPersisted } from './connectors';
import type { User } from './user';
diff --git a/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts b/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts
index 123a3b987bc07..27e453993e994 100644
--- a/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts
+++ b/x-pack/plugins/cases/server/routes/api/configure/get_configure.ts
@@ -6,9 +6,9 @@
*/
import { CASE_CONFIGURE_URL } from '../../../../common/constants';
-import type { GetConfigurationFindRequest } from '../../../../common/api';
import { createCaseError } from '../../../common/error';
import { createCasesRoute } from '../create_cases_route';
+import type { configureApiV1 } from '../../../../common/types/api';
export const getCaseConfigureRoute = createCasesRoute({
method: 'get',
@@ -17,10 +17,12 @@ export const getCaseConfigureRoute = createCasesRoute({
try {
const caseContext = await context.cases;
const client = await caseContext.getCasesClient();
- const options = request.query as GetConfigurationFindRequest;
+ const options = request.query as configureApiV1.GetConfigurationFindRequest;
+
+ const res: configureApiV1.GetConfigureResponse = await client.configure.get({ ...options });
return response.ok({
- body: await client.configure.get({ ...options }),
+ body: res,
});
} catch (error) {
throw createCaseError({
diff --git a/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts b/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts
index 55e7ede2abd06..2ab05277891ed 100644
--- a/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts
+++ b/x-pack/plugins/cases/server/routes/api/configure/patch_configure.ts
@@ -5,11 +5,12 @@
* 2.0.
*/
-import type { ConfigurationPatchRequest } from '../../../../common/api';
-import { CaseConfigureRequestParamsRt, decodeWithExcessOrThrow } from '../../../../common/api';
+import { CaseConfigureRequestParamsRt } from '../../../../common/types/api';
+import { decodeWithExcessOrThrow } from '../../../../common/api';
import { CASE_CONFIGURE_DETAILS_URL } from '../../../../common/constants';
import { createCaseError } from '../../../common/error';
import { createCasesRoute } from '../create_cases_route';
+import type { configureApiV1 } from '../../../../common/types/api';
export const patchCaseConfigureRoute = createCasesRoute({
method: 'patch',
@@ -20,10 +21,14 @@ export const patchCaseConfigureRoute = createCasesRoute({
const caseContext = await context.cases;
const client = await caseContext.getCasesClient();
- const configuration = request.body as ConfigurationPatchRequest;
+ const configuration = request.body as configureApiV1.ConfigurationPatchRequest;
+ const res: configureApiV1.UpdateConfigureResponse = await client.configure.update(
+ params.configuration_id,
+ configuration
+ );
return response.ok({
- body: await client.configure.update(params.configuration_id, configuration),
+ body: res,
});
} catch (error) {
throw createCaseError({
diff --git a/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts b/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts
index bb15059f541b1..b4d831df75537 100644
--- a/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts
+++ b/x-pack/plugins/cases/server/routes/api/configure/post_configure.ts
@@ -5,10 +5,12 @@
* 2.0.
*/
-import { ConfigurationRequestRt, decodeWithExcessOrThrow } from '../../../../common/api';
+import { ConfigurationRequestRt } from '../../../../common/types/api';
+import { decodeWithExcessOrThrow } from '../../../../common/api';
import { CASE_CONFIGURE_URL } from '../../../../common/constants';
import { createCaseError } from '../../../common/error';
import { createCasesRoute } from '../create_cases_route';
+import type { configureApiV1 } from '../../../../common/types/api';
export const postCaseConfigureRoute = createCasesRoute({
method: 'post',
@@ -19,9 +21,10 @@ export const postCaseConfigureRoute = createCasesRoute({
const caseContext = await context.cases;
const client = await caseContext.getCasesClient();
+ const res: configureApiV1.CreateConfigureResponse = await client.configure.create(query);
return response.ok({
- body: await client.configure.create(query),
+ body: res,
});
} catch (error) {
throw createCaseError({
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts
index 2d3ffbc38242f..a5285817b2688 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts
@@ -7,7 +7,6 @@
import type { SavedObjectSanitizedDoc, SavedObjectUnsanitizedDoc } from '@kbn/core/server';
import { ACTION_SAVED_OBJECT_TYPE } from '@kbn/actions-plugin/server';
-import type { ConfigurationAttributes } from '../../../common/api';
import { ConnectorTypes } from '../../../common/api';
import { CASE_CONFIGURE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { CONNECTOR_ID_REFERENCE_NAME } from '../../common/constants';
@@ -16,6 +15,7 @@ import type { ESCaseConnectorWithId } from '../../services/test_utils';
import type { UnsanitizedConfigureConnector } from './configuration';
import { createConnectorAttributeMigration, configureConnectorIdMigration } from './configuration';
import type { ConfigurationPersistedAttributes } from '../../common/types/configure';
+import type { ConfigurationAttributes } from '../../../common/types/domain';
// eslint-disable-next-line @typescript-eslint/naming-convention
const create_7_14_0_configSchema = (connector?: ESCaseConnectorWithId) => ({
diff --git a/x-pack/plugins/cases/server/services/configure/index.test.ts b/x-pack/plugins/cases/server/services/configure/index.test.ts
index ff7d37d861c38..ce9c3f68a604f 100644
--- a/x-pack/plugins/cases/server/services/configure/index.test.ts
+++ b/x-pack/plugins/cases/server/services/configure/index.test.ts
@@ -5,11 +5,7 @@
* 2.0.
*/
-import type {
- CaseConnector,
- ConfigurationAttributes,
- ConfigurationPatchRequest,
-} from '../../../common/api';
+import type { CaseConnector } from '../../../common/api';
import { ConnectorTypes } from '../../../common/api';
import { CASE_CONFIGURE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants';
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
@@ -30,6 +26,8 @@ import type { ESCaseConnectorWithId } from '../test_utils';
import { createESJiraConnector, createJiraConnector } from '../test_utils';
import type { ConfigurationPersistedAttributes } from '../../common/types/configure';
import { unset } from 'lodash';
+import type { ConfigurationPatchRequest } from '../../../common/types/api';
+import type { ConfigurationAttributes } from '../../../common/types/domain';
const basicConfigFields = {
closure_type: 'close-by-pushing' as const,
diff --git a/x-pack/plugins/cases/server/services/configure/index.ts b/x-pack/plugins/cases/server/services/configure/index.ts
index 083d7aeb19ce2..6860f9edc940d 100644
--- a/x-pack/plugins/cases/server/services/configure/index.ts
+++ b/x-pack/plugins/cases/server/services/configure/index.ts
@@ -13,8 +13,8 @@ import type {
} from '@kbn/core/server';
import { ACTION_SAVED_OBJECT_TYPE } from '@kbn/actions-plugin/server';
+import type { ConfigurationAttributes } from '../../../common/types/domain';
import { CONNECTOR_ID_REFERENCE_NAME } from '../../common/constants';
-import type { ConfigurationAttributes } from '../../../common/api';
import { decodeOrThrow } from '../../../common/api';
import { CASE_CONFIGURE_SAVED_OBJECT } from '../../../common/constants';
import {
diff --git a/x-pack/plugins/cases/server/services/configure/types.ts b/x-pack/plugins/cases/server/services/configure/types.ts
index 2697d02c84c80..f6f325d0280e5 100644
--- a/x-pack/plugins/cases/server/services/configure/types.ts
+++ b/x-pack/plugins/cases/server/services/configure/types.ts
@@ -6,7 +6,7 @@
*/
import type { SavedObject, SavedObjectsClientContract } from '@kbn/core/server';
-import type { ConfigurationAttributes } from '../../../common/api';
+import type { ConfigurationAttributes } from '../../../common/types/domain';
import type { IndexRefresh } from '../types';
import type { SavedObjectFindOptionsKueryNode } from '../../common/types';
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx
index 2749f83703072..1fca8bed5272e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx
@@ -19,6 +19,7 @@ import { generateEncodedPath } from '../../../../../shared/encode_path_params';
import { EuiButtonTo } from '../../../../../shared/react_router_helpers';
import { SEARCH_INDEX_TAB_PATH } from '../../../../routes';
+import { SyncsContextMenu } from '../../components/header_actions/syncs_context_menu';
import { IndexNameLogic } from '../../index_name_logic';
import { SearchIndexTabId } from '../../search_index';
@@ -54,6 +55,9 @@ export const NativeConnectorAdvancedConfiguration: React.FC = () => {
)}
+
+
+
diff --git a/x-pack/plugins/fleet/common/experimental_features.ts b/x-pack/plugins/fleet/common/experimental_features.ts
index 1d2929eb00dee..626bb4ab7f9cd 100644
--- a/x-pack/plugins/fleet/common/experimental_features.ts
+++ b/x-pack/plugins/fleet/common/experimental_features.ts
@@ -16,7 +16,6 @@ export const allowedExperimentalValues = Object.freeze({
packageVerification: true,
showDevtoolsRequest: true,
diagnosticFileUploadEnabled: true,
- experimentalDataStreamSettings: false,
displayAgentMetrics: true,
showIntegrationsSubcategories: true,
agentFqdnMode: true,
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.test.tsx
deleted file mode 100644
index 0b5cdc0f6ad91..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.test.tsx
+++ /dev/null
@@ -1,260 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-import { act, fireEvent } from '@testing-library/react';
-
-import { createFleetTestRendererMock } from '../../../../../../../../mock';
-
-import type { RegistryDataStream } from '../../../../../../../../../common/types';
-
-import { ExperimentDatastreamSettings } from './experimental_datastream_settings';
-
-describe('ExperimentDatastreamSettings', () => {
- describe('Synthetic source', () => {
- it('should be enabled an not checked by default', () => {
- const mockSetNewExperimentalDataFeatures = jest.fn();
- const res = createFleetTestRendererMock().render(
-
- );
-
- const syntheticSourceSwitch = res.getByTestId(
- 'packagePolicyEditor.syntheticSourceExperimentalFeature.switch'
- );
- expect(syntheticSourceSwitch).not.toBeChecked();
- expect(syntheticSourceSwitch).toBeEnabled();
- expect(mockSetNewExperimentalDataFeatures).not.toBeCalled();
- });
-
- it('should be checked if the regitry datastream define source_mode synthetic', () => {
- const mockSetNewExperimentalDataFeatures = jest.fn();
- const res = createFleetTestRendererMock().render(
-
- );
-
- const syntheticSourceSwitch = res.getByTestId(
- 'packagePolicyEditor.syntheticSourceExperimentalFeature.switch'
- );
- expect(syntheticSourceSwitch).toBeChecked();
- expect(syntheticSourceSwitch).toBeEnabled();
- expect(mockSetNewExperimentalDataFeatures).not.toBeCalled();
- });
-
- it('should be not checked and disabled if the regitry datastream define source_mode synthetic and the user disabled it', () => {
- const mockSetNewExperimentalDataFeatures = jest.fn();
- const res = createFleetTestRendererMock().render(
-
- );
-
- const syntheticSourceSwitch = res.getByTestId(
- 'packagePolicyEditor.syntheticSourceExperimentalFeature.switch'
- );
- expect(syntheticSourceSwitch).not.toBeChecked();
- expect(syntheticSourceSwitch).toBeEnabled();
- expect(mockSetNewExperimentalDataFeatures).not.toBeCalled();
- });
-
- it('should not be checked and not enabled if the regitry datastream define source_mode default', () => {
- const mockSetNewExperimentalDataFeatures = jest.fn();
- const res = createFleetTestRendererMock().render(
-
- );
-
- const syntheticSourceSwitch = res.getByTestId(
- 'packagePolicyEditor.syntheticSourceExperimentalFeature.switch'
- );
- expect(syntheticSourceSwitch).not.toBeChecked();
- expect(syntheticSourceSwitch).not.toBeEnabled();
- expect(mockSetNewExperimentalDataFeatures).not.toBeCalled();
- });
-
- it('should not mutate original experimental feature if a user change one', () => {
- const mockSetNewExperimentalDataFeatures = jest.fn();
- const experimentalDataFeatures = [
- {
- data_stream: 'logs-test',
- features: {
- synthetic_source: true,
- tsdb: false,
- doc_value_only_numeric: false,
- doc_value_only_other: false,
- },
- },
- ];
- const res = createFleetTestRendererMock().render(
-
- );
-
- act(() => {
- fireEvent.click(
- res.getByTestId('packagePolicyEditor.syntheticSourceExperimentalFeature.switch')
- );
- });
- expect(mockSetNewExperimentalDataFeatures).toBeCalled();
- expect(mockSetNewExperimentalDataFeatures.mock.calls[0][0]).toEqual([
- {
- data_stream: 'logs-test',
- features: {
- synthetic_source: false,
- tsdb: false,
- doc_value_only_numeric: false,
- doc_value_only_other: false,
- },
- },
- ]);
- });
- });
- describe('TSDB', () => {
- it('should be enabled and unchecked by default', () => {
- const mockSetNewExperimentalDataFeatures = jest.fn();
- const res = createFleetTestRendererMock().render(
-
- );
-
- const tsdbSwitch = res.getByTestId('packagePolicyEditor.tsdbExperimentalFeature.switch');
-
- expect(tsdbSwitch).toBeEnabled();
- expect(tsdbSwitch).not.toBeChecked();
- expect(mockSetNewExperimentalDataFeatures).not.toBeCalled();
- });
-
- it('should be disabled and checked with a tooltip if registry data streams includes "elasticsearch.index_mode: time_series"', () => {
- const mockSetNewExperimentalDataFeatures = jest.fn();
- const res = createFleetTestRendererMock().render(
-
- );
-
- const tsdbSwitch = res.getByTestId(
- 'packagePolicyEditor.tsdbExperimentalFeature.switchTooltip'
- );
-
- expect(tsdbSwitch).toBeDisabled();
- expect(tsdbSwitch).toBeChecked();
- expect(mockSetNewExperimentalDataFeatures).not.toBeCalled();
- });
-
- it('should not mutate other experimental features when changed', () => {
- const experimentalDataFeatures = [
- {
- data_stream: 'logs-test',
- features: {
- doc_value_only_numeric: false,
- doc_value_only_other: false,
- synthetic_source: true,
- tsdb: false,
- },
- },
- ];
-
- const mockSetNewExperimentalDataFeatures = jest.fn();
- const res = createFleetTestRendererMock().render(
-
- );
-
- act(() => {
- fireEvent.click(res.getByTestId('packagePolicyEditor.tsdbExperimentalFeature.switch'));
- });
-
- expect(mockSetNewExperimentalDataFeatures).toBeCalled();
- expect(mockSetNewExperimentalDataFeatures.mock.calls[0][0]).toEqual([
- {
- data_stream: 'logs-test',
- features: {
- doc_value_only_numeric: false,
- doc_value_only_other: false,
- synthetic_source: true,
- tsdb: true,
- },
- },
- ]);
- });
- });
-});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.tsx
deleted file mode 100644
index 5827b0471dd58..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/experimental_datastream_settings.tsx
+++ /dev/null
@@ -1,277 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import React from 'react';
-
-import { FormattedMessage } from '@kbn/i18n-react';
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiSwitch,
- EuiText,
- EuiSpacer,
- EuiTitle,
- EuiLink,
- EuiIconTip,
-} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-
-import { useStartServices } from '../../../../../../../../hooks';
-
-import type {
- ExperimentalDataStreamFeature,
- RegistryDataStream,
-} from '../../../../../../../../../common/types';
-import { getRegistryDataStreamAssetBaseName } from '../../../../../../../../../common/services';
-import type { ExperimentalIndexingFeature } from '../../../../../../../../../common/types/models/epm';
-
-interface Props {
- registryDataStream: RegistryDataStream;
- experimentalDataFeatures?: ExperimentalDataStreamFeature[];
- setNewExperimentalDataFeatures: (
- experimentalDataFeatures: ExperimentalDataStreamFeature[]
- ) => void;
-}
-
-function getExperimentalFeatureValue(
- feature: ExperimentalIndexingFeature,
- experimentalDataFeatures: ExperimentalDataStreamFeature[],
- registryDataStream: RegistryDataStream
-) {
- return experimentalDataFeatures?.find(
- ({ data_stream: dataStream, features }) =>
- dataStream === getRegistryDataStreamAssetBaseName(registryDataStream) &&
- typeof features[feature] !== 'undefined'
- )?.features?.[feature];
-}
-
-export const ExperimentDatastreamSettings: React.FunctionComponent = ({
- registryDataStream,
- experimentalDataFeatures,
- setNewExperimentalDataFeatures,
-}) => {
- const { docLinks } = useStartServices();
-
- const isSyntheticSourceEditable = registryDataStream.elasticsearch?.source_mode !== 'default';
-
- const syntheticSourceExperimentalValue = getExperimentalFeatureValue(
- 'synthetic_source',
- experimentalDataFeatures ?? [],
- registryDataStream
- );
-
- const isTimeSeriesEnabledByDefault =
- registryDataStream.elasticsearch?.index_mode === 'time_series';
-
- const isSyntheticSourceEnabledByDefault =
- registryDataStream.elasticsearch?.source_mode === 'synthetic' || isTimeSeriesEnabledByDefault;
-
- const docValueOnlyNumericExperimentalValue = getExperimentalFeatureValue(
- 'doc_value_only_numeric',
- experimentalDataFeatures ?? [],
- registryDataStream
- );
-
- const docValueOnlyOtherExperimentalValue = getExperimentalFeatureValue(
- 'doc_value_only_other',
- experimentalDataFeatures ?? [],
- registryDataStream
- );
-
- const newExperimentalIndexingFeature = {
- synthetic_source:
- typeof syntheticSourceExperimentalValue !== 'undefined'
- ? syntheticSourceExperimentalValue
- : isSyntheticSourceEnabledByDefault,
- tsdb: isTimeSeriesEnabledByDefault
- ? isTimeSeriesEnabledByDefault
- : getExperimentalFeatureValue('tsdb', experimentalDataFeatures ?? [], registryDataStream) ??
- false,
- doc_value_only_numeric:
- typeof docValueOnlyNumericExperimentalValue !== 'undefined'
- ? docValueOnlyNumericExperimentalValue
- : false,
- doc_value_only_other:
- typeof docValueOnlyOtherExperimentalValue !== 'undefined'
- ? docValueOnlyOtherExperimentalValue
- : false,
- };
-
- const onIndexingSettingChange = (
- features: Partial>
- ) => {
- const newExperimentalDataStreamFeatures =
- experimentalDataFeatures?.map((feature) => ({ ...feature })) ?? [];
-
- const dataStream = getRegistryDataStreamAssetBaseName(registryDataStream);
-
- const existingSettingRecord = newExperimentalDataStreamFeatures.find(
- (x) => x.data_stream === dataStream
- );
- if (existingSettingRecord) {
- existingSettingRecord.features = {
- ...existingSettingRecord.features,
- ...features,
- };
- } else {
- newExperimentalDataStreamFeatures.push({
- data_stream: dataStream,
- features: { ...newExperimentalIndexingFeature, ...features },
- });
- }
-
- setNewExperimentalDataFeatures(newExperimentalDataStreamFeatures);
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {i18n.translate(
- 'xpack.fleet.packagePolicyEditor.experimentalFeatureRolloverLearnMore',
- { defaultMessage: 'Learn more' }
- )}
-
- ),
- }}
- />
-
-
-
-
-
-
- }
- onChange={(e) => {
- onIndexingSettingChange({
- synthetic_source: e.target.checked,
- });
- }}
- />
-
-
- {isTimeSeriesEnabledByDefault ? (
-
-
-
- }
- onChange={(e) => {
- onIndexingSettingChange({
- tsdb: e.target.checked,
- });
- }}
- />
-
-
-
-
-
- ) : (
-
- }
- onChange={(e) => {
- onIndexingSettingChange({
- tsdb: e.target.checked,
- });
- }}
- />
- )}
-
-
-
- }
- onChange={(e) => {
- onIndexingSettingChange({
- doc_value_only_numeric: e.target.checked,
- });
- }}
- />
-
-
-
- }
- onChange={(e) => {
- onIndexingSettingChange({
- doc_value_only_other: e.target.checked,
- });
- }}
- />
-
-
-
- );
-};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx
index fdf4819cf1bbe..1a13bbcf25367 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/steps/components/package_policy_input_stream.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useState, Fragment, memo, useMemo, useEffect, useRef, useCallback } from 'react';
+import React, { useState, Fragment, memo, useMemo, useEffect, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import styled from 'styled-components';
import { uniq } from 'lodash';
@@ -31,7 +31,6 @@ import {
getRegistryDataStreamAssetBaseName,
mapPackageReleaseToIntegrationCardRelease,
} from '../../../../../../../../../common/services';
-import type { ExperimentalDataStreamFeature } from '../../../../../../../../../common/types/models/epm';
import type {
NewPackagePolicy,
@@ -48,7 +47,6 @@ import { PackagePolicyEditorDatastreamMappings } from '../../datastream_mappings
import { useIndexTemplateExists } from '../../datastream_hooks';
-import { ExperimentDatastreamSettings } from './experimental_datastream_settings';
import { PackagePolicyInputVarField } from './package_policy_input_var_field';
import { useDataStreamId } from './hooks';
import { sortDatastreamsByDataset } from './sort_datastreams';
@@ -150,22 +148,6 @@ export const PackagePolicyInputStreamConfig = memo(
[advancedVars, inputStreamValidationResults?.vars]
);
- const setNewExperimentalDataFeatures = useCallback(
- (newFeatures: ExperimentalDataStreamFeature[]) => {
- if (!packagePolicy.package) {
- return;
- }
-
- updatePackagePolicy({
- package: {
- ...packagePolicy.package,
- experimental_data_stream_features: newFeatures,
- },
- });
- },
- [updatePackagePolicy, packagePolicy]
- );
-
const { data: dataStreamsData } = useQuery(['datastreams'], () => sendGetDataStreams(), {
enabled: packageInfo.type === 'input', // Only fetch datastream for input type package
});
@@ -357,16 +339,6 @@ export const PackagePolicyInputStreamConfig = memo(
>
)}
- {/* Experimental index/datastream settings e.g. synthetic source */}
- {isExperimentalDataStreamSettingsEnabled && (
-
- )}
>
) : null}
diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts
index 1c54e543e7747..f3b332a5930fc 100644
--- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts
+++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.test.ts
@@ -29,6 +29,7 @@ import {
} from './mocks';
import {
bulkCreateArtifacts,
+ bulkDeleteArtifacts,
createArtifact,
deleteArtifact,
encodeArtifactContent,
@@ -348,6 +349,54 @@ describe('When using the artifacts services', () => {
});
});
+ describe('and calling `bulkDeleteArtifacts()`', () => {
+ it('should delete single artifact', async () => {
+ bulkDeleteArtifacts(esClientMock, ['123']);
+
+ expect(esClientMock.bulk).toHaveBeenCalledWith({
+ refresh: 'wait_for',
+ body: [
+ {
+ delete: {
+ _id: '123',
+ _index: FLEET_SERVER_ARTIFACTS_INDEX,
+ },
+ },
+ ],
+ });
+ });
+
+ it('should delete all the artifacts', async () => {
+ bulkDeleteArtifacts(esClientMock, ['123', '231']);
+
+ expect(esClientMock.bulk).toHaveBeenCalledWith({
+ refresh: 'wait_for',
+ body: [
+ {
+ delete: {
+ _id: '123',
+ _index: FLEET_SERVER_ARTIFACTS_INDEX,
+ },
+ },
+ {
+ delete: {
+ _id: '231',
+ _index: FLEET_SERVER_ARTIFACTS_INDEX,
+ },
+ },
+ ],
+ });
+ });
+
+ it('should throw an ArtifactElasticsearchError if one is encountered', async () => {
+ setEsClientMethodResponseToError(esClientMock, 'bulk');
+
+ await expect(bulkDeleteArtifacts(esClientMock, ['123'])).rejects.toBeInstanceOf(
+ ArtifactsElasticsearchError
+ );
+ });
+ });
+
describe('and calling `listArtifacts()`', () => {
beforeEach(() => {
esClientMock.search.mockResponse(generateArtifactEsSearchResultHitsMock());
diff --git a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts
index 0e312f142edf6..5516ab6f70e23 100644
--- a/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts
+++ b/x-pack/plugins/fleet/server/services/artifacts/artifacts.ts
@@ -197,6 +197,37 @@ export const deleteArtifact = async (esClient: ElasticsearchClient, id: string):
}
};
+export const bulkDeleteArtifacts = async (
+ esClient: ElasticsearchClient,
+ ids: string[]
+): Promise => {
+ try {
+ const body = ids.map((id) => ({
+ delete: { _index: FLEET_SERVER_ARTIFACTS_INDEX, _id: id },
+ }));
+
+ const res = await withPackageSpan(`Bulk delete fleet artifacts`, () =>
+ esClient.bulk({
+ body,
+ refresh: 'wait_for',
+ })
+ );
+ let errors: Error[] = [];
+ // Track errors of the bulk delete action
+ if (res.errors) {
+ errors = res.items.reduce((acc, item) => {
+ if (item.delete?.error) {
+ acc.push(new Error(item.delete.error.reason));
+ }
+ return acc;
+ }, []);
+ }
+ return errors;
+ } catch (e) {
+ throw new ArtifactsElasticsearchError(e);
+ }
+};
+
export const listArtifacts = async (
esClient: ElasticsearchClient,
options: ListArtifactsProps = {}
diff --git a/x-pack/plugins/fleet/server/services/artifacts/client.test.ts b/x-pack/plugins/fleet/server/services/artifacts/client.test.ts
index 1a242c83275fd..61d07f0a3e99c 100644
--- a/x-pack/plugins/fleet/server/services/artifacts/client.test.ts
+++ b/x-pack/plugins/fleet/server/services/artifacts/client.test.ts
@@ -7,6 +7,8 @@
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
+import { FLEET_SERVER_ARTIFACTS_INDEX } from '../../../common/constants';
+
import { ArtifactsClientAccessDeniedError, ArtifactsClientError } from '../../errors';
import { appContextService } from '../app_context';
@@ -155,6 +157,25 @@ describe('When using the Fleet Artifacts Client', () => {
});
});
+ describe('and calling `bulkDeleteArtifacts()`', () => {
+ it('should bulk delete the artifact', async () => {
+ setEsClientGetMock();
+ await artifactClient.bulkDeleteArtifacts(['123']);
+ expect(esClientMock.bulk).toHaveBeenCalledWith(
+ expect.objectContaining({
+ body: [
+ {
+ delete: {
+ _id: 'endpoint:123',
+ _index: FLEET_SERVER_ARTIFACTS_INDEX,
+ },
+ },
+ ],
+ })
+ );
+ });
+ });
+
describe('and calling `listArtifacts()`', () => {
beforeEach(() => {
esClientMock.search.mockResponse(generateArtifactEsSearchResultHitsMock());
diff --git a/x-pack/plugins/fleet/server/services/artifacts/client.ts b/x-pack/plugins/fleet/server/services/artifacts/client.ts
index 3cf28e4847d0d..7ba2452e83fe7 100644
--- a/x-pack/plugins/fleet/server/services/artifacts/client.ts
+++ b/x-pack/plugins/fleet/server/services/artifacts/client.ts
@@ -18,7 +18,7 @@ import type {
NewArtifact,
ListArtifactsProps,
} from './types';
-import { relativeDownloadUrlFromArtifact } from './mappings';
+import { relativeDownloadUrlFromArtifact, uniqueIdFromId } from './mappings';
import {
createArtifact,
@@ -28,6 +28,7 @@ import {
getArtifact,
listArtifacts,
bulkCreateArtifacts,
+ bulkDeleteArtifacts,
} from './artifacts';
/**
@@ -112,6 +113,11 @@ export class FleetArtifactsClient implements ArtifactsClientInterface {
}
}
+ async bulkDeleteArtifacts(ids: string[]): Promise {
+ const idsMappedWithPackageName = ids.map((id) => uniqueIdFromId(id, this.packageName));
+ return await bulkDeleteArtifacts(this.esClient, idsMappedWithPackageName);
+ }
+
/**
* Get a list of artifacts.
* NOTE that when using the `kuery` filtering param, that all filters property names should
diff --git a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts
index 09b804a208342..3645f957417e3 100644
--- a/x-pack/plugins/fleet/server/services/artifacts/mappings.ts
+++ b/x-pack/plugins/fleet/server/services/artifacts/mappings.ts
@@ -82,3 +82,7 @@ export const uniqueIdFromArtifact = <
}: T): string => {
return `${packageName}:${identifier}-${decodedSha256}`;
};
+
+export const uniqueIdFromId = (id: string, packageName: string): string => {
+ return `${packageName}:${id}`;
+};
diff --git a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts
index 26dc93181a102..dc831558cb7bb 100644
--- a/x-pack/plugins/fleet/server/services/artifacts/mocks.ts
+++ b/x-pack/plugins/fleet/server/services/artifacts/mocks.ts
@@ -26,6 +26,7 @@ export const createArtifactsClientMock = (): jest.Mocked>(
};
type EsClientMock = ReturnType;
-type EsClientMockMethods = keyof Pick;
+type EsClientMockMethods = keyof Pick<
+ EsClientMock,
+ 'get' | 'create' | 'delete' | 'search' | 'bulk'
+>;
export const setEsClientMethodResponseToError = (
esClientMock: EsClientMock,
diff --git a/x-pack/plugins/fleet/server/services/artifacts/types.ts b/x-pack/plugins/fleet/server/services/artifacts/types.ts
index 05ba89b3bb0ff..4b0aacd92bc20 100644
--- a/x-pack/plugins/fleet/server/services/artifacts/types.ts
+++ b/x-pack/plugins/fleet/server/services/artifacts/types.ts
@@ -86,6 +86,8 @@ export interface ArtifactsClientInterface {
deleteArtifact(id: string): Promise;
+ bulkDeleteArtifacts(ids: string[]): Promise;
+
listArtifacts(options?: ListArtifactsProps): Promise>;
encodeContent(content: ArtifactsClientCreateOptions['content']): Promise;
diff --git a/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx
new file mode 100644
index 0000000000000..b2223eccc2451
--- /dev/null
+++ b/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx
@@ -0,0 +1,155 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import './toolbar.scss';
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import type { PaletteRegistry } from '@kbn/coloring';
+import { ColorPicker, useDebouncedValue } from '@kbn/visualization-ui-components/public';
+import { PieVisualizationState } from '../../../common/types';
+import { VisualizationDimensionEditorProps } from '../../types';
+import { PalettePicker } from '../../shared_components';
+import { CollapseSetting } from '../../shared_components/collapse_setting';
+import {
+ getDefaultColorForMultiMetricDimension,
+ hasNonCollapsedSliceBy,
+ isCollapsed,
+} from './visualization';
+
+type DimensionEditorProps = VisualizationDimensionEditorProps & {
+ paletteService: PaletteRegistry;
+};
+
+export function DimensionEditor(props: DimensionEditorProps) {
+ const { inputValue: localState, handleInputChange: setLocalState } =
+ useDebouncedValue({
+ value: props.state,
+ onChange: props.setState,
+ });
+
+ const currentLayer = localState.layers.find((layer) => layer.layerId === props.layerId);
+
+ const setConfig = React.useCallback(
+ ({ color }) => {
+ if (!currentLayer) {
+ return;
+ }
+ const newColorsByDimension = { ...currentLayer.colorsByDimension };
+
+ if (color) {
+ newColorsByDimension[props.accessor] = color;
+ } else {
+ delete newColorsByDimension[props.accessor];
+ }
+
+ setLocalState({
+ ...localState,
+ layers: localState.layers.map((layer) =>
+ layer.layerId === currentLayer.layerId
+ ? {
+ ...layer,
+ colorsByDimension: newColorsByDimension,
+ }
+ : layer
+ ),
+ });
+ },
+ [currentLayer, localState, props.accessor, setLocalState]
+ );
+
+ if (!currentLayer) {
+ return null;
+ }
+
+ const firstNonCollapsedColumnId = currentLayer.primaryGroups.find(
+ (id) => !isCollapsed(id, currentLayer)
+ );
+
+ const showColorPicker =
+ currentLayer.metrics.includes(props.accessor) && currentLayer.allowMultipleMetrics;
+
+ const colorPickerDisabledMessage = hasNonCollapsedSliceBy(currentLayer)
+ ? ['pie', 'donut'].includes(props.state.shape)
+ ? i18n.translate('xpack.lens.pieChart.colorPicker.disabledBecauseSliceBy', {
+ defaultMessage:
+ 'You are unable to apply custom colors to individual slices when the layer includes one or more "Slice by" dimensions.',
+ })
+ : i18n.translate('xpack.lens.pieChart.colorPicker.disabledBecauseGroupBy', {
+ defaultMessage:
+ 'You are unable to apply custom colors to individual slices when the layer includes one or more "Group by" dimensions.',
+ })
+ : undefined;
+
+ return (
+ <>
+ {props.accessor === firstNonCollapsedColumnId && (
+ {
+ setLocalState({ ...props.state, palette: newPalette });
+ }}
+ />
+ )}
+ {showColorPicker && (
+
+ )}
+ >
+ );
+}
+
+export function DimensionDataExtraEditor(
+ props: VisualizationDimensionEditorProps & {
+ paletteService: PaletteRegistry;
+ }
+) {
+ const currentLayer = props.state.layers.find((layer) => layer.layerId === props.layerId);
+
+ if (!currentLayer) {
+ return null;
+ }
+
+ return (
+ <>
+ {[...currentLayer.primaryGroups, ...(currentLayer.secondaryGroups ?? [])].includes(
+ props.accessor
+ ) && (
+ {
+ props.setState({
+ ...props.state,
+ layers: props.state.layers.map((layer) =>
+ layer.layerId !== props.layerId
+ ? layer
+ : {
+ ...layer,
+ collapseFns: {
+ ...layer.collapseFns,
+ [props.accessor]: collapseFn,
+ },
+ }
+ ),
+ });
+ }}
+ />
+ )}
+ >
+ );
+}
diff --git a/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts b/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts
index e2360de8ff67c..c592f7d369eb3 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts
+++ b/x-pack/plugins/lens/public/visualizations/partition/to_expression.ts
@@ -34,7 +34,7 @@ import {
PieChartTypes,
} from '../../../common/constants';
import { getDefaultVisualValuesForLayer } from '../../shared_components/datasource_default_values';
-import { isCollapsed } from './visualization';
+import { hasNonCollapsedSliceBy, isCollapsed } from './visualization';
interface Attributes {
isPreview: boolean;
@@ -110,7 +110,7 @@ const generateCommonLabelsAstArgs: GenerateLabelsAstArguments = (
layer.numberDisplay !== NumberDisplay.HIDDEN ? (layer.numberDisplay as ValueFormats) : [];
const percentDecimals = layer.percentDecimals ?? DEFAULT_PERCENT_DECIMALS;
const colorOverrides =
- layer.allowMultipleMetrics && !layer.primaryGroups.length
+ layer.allowMultipleMetrics && !hasNonCollapsedSliceBy(layer)
? Object.entries(columnToLabelMap).reduce>(
(acc, [columnId, label]) => {
const color = layer.colorsByDimension?.[columnId];
diff --git a/x-pack/plugins/lens/public/visualizations/partition/toolbar.tsx b/x-pack/plugins/lens/public/visualizations/partition/toolbar.tsx
index f25a97d7d7500..2596f28d8c801 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/toolbar.tsx
+++ b/x-pack/plugins/lens/public/visualizations/partition/toolbar.tsx
@@ -15,24 +15,18 @@ import {
EuiRange,
EuiHorizontalRule,
EuiButtonGroup,
- EuiColorPicker,
- euiPaletteColorBlind,
- EuiToolTip,
} from '@elastic/eui';
import type { Position } from '@elastic/charts';
-import type { PaletteRegistry } from '@kbn/coloring';
import { LegendSize } from '@kbn/visualizations-plugin/public';
import { useDebouncedValue } from '@kbn/visualization-ui-components/public';
import { DEFAULT_PERCENT_DECIMALS } from './constants';
import { PartitionChartsMeta } from './partition_charts_meta';
-import { PieLayerState, PieVisualizationState, SharedPieLayerState } from '../../../common/types';
+import { PieVisualizationState, SharedPieLayerState } from '../../../common/types';
import { LegendDisplay } from '../../../common/constants';
-import { VisualizationDimensionEditorProps, VisualizationToolbarProps } from '../../types';
-import { ToolbarPopover, LegendSettingsPopover, PalettePicker } from '../../shared_components';
+import { VisualizationToolbarProps } from '../../types';
+import { ToolbarPopover, LegendSettingsPopover } from '../../shared_components';
import { getDefaultVisualValuesForLayer } from '../../shared_components/datasource_default_values';
import { shouldShowValuesInLegend } from './render_helpers';
-import { CollapseSetting } from '../../shared_components/collapse_setting';
-import { getDefaultColorForMultiMetricDimension, isCollapsed } from './visualization';
const legendOptions: Array<{
value: SharedPieLayerState['legendDisplay'];
@@ -305,183 +299,3 @@ const DecimalPlaceSlider = ({
/>
);
};
-
-type DimensionEditorProps = VisualizationDimensionEditorProps & {
- paletteService: PaletteRegistry;
-};
-
-export function DimensionEditor(props: DimensionEditorProps) {
- const currentLayer = props.state.layers.find((layer) => layer.layerId === props.layerId);
-
- if (!currentLayer) {
- return null;
- }
-
- const firstNonCollapsedColumnId = currentLayer.primaryGroups.find(
- (id) => !isCollapsed(id, currentLayer)
- );
-
- const showColorPicker =
- currentLayer.metrics.includes(props.accessor) && currentLayer.allowMultipleMetrics;
-
- return (
- <>
- {props.accessor === firstNonCollapsedColumnId && (
- {
- props.setState({ ...props.state, palette: newPalette });
- }}
- />
- )}
- {showColorPicker && }
- >
- );
-}
-
-function StaticColorControls({
- state,
- paletteService,
- accessor,
- setState,
- datasource,
- currentLayer,
-}: DimensionEditorProps & { currentLayer: PieLayerState }) {
- const colorLabel = i18n.translate('xpack.lens.pieChart.color', {
- defaultMessage: 'Color',
- });
-
- const disabledMessage = currentLayer.primaryGroups.length
- ? ['pie', 'donut'].includes(state.shape)
- ? i18n.translate('xpack.lens.pieChart.colorPicker.disabledBecauseSliceBy', {
- defaultMessage:
- 'You are unable to apply custom colors to individual slices when the layer includes one or more "Slice by" dimensions.',
- })
- : i18n.translate('xpack.lens.pieChart.colorPicker.disabledBecauseGroupBy', {
- defaultMessage:
- 'You are unable to apply custom colors to individual slices when the layer includes one or more "Group by" dimensions.',
- })
- : '';
-
- const defaultColor = getDefaultColorForMultiMetricDimension({
- layer: currentLayer,
- columnId: accessor,
- paletteService,
- datasource,
- });
-
- const setColor = useCallback(
- (color: string) => {
- const newColorsByDimension = { ...currentLayer.colorsByDimension };
-
- if (color) {
- newColorsByDimension[accessor] = color;
- } else {
- delete newColorsByDimension[accessor];
- }
-
- setState({
- ...state,
- layers: state.layers.map((layer) =>
- layer.layerId === currentLayer.layerId
- ? {
- ...layer,
- colorsByDimension: newColorsByDimension,
- }
- : layer
- ),
- });
- },
- [accessor, currentLayer.colorsByDimension, currentLayer.layerId, setState, state]
- );
-
- const { inputValue: currentColor, handleInputChange: handleColorChange } =
- useDebouncedValue(
- {
- onChange: setColor,
- value: currentLayer.colorsByDimension?.[accessor] || defaultColor,
- },
- { allowFalsyValue: true }
- );
-
- const isDisabled = Boolean(disabledMessage);
-
- const renderColorPicker = () => (
- handleColorChange(color)}
- color={isDisabled ? '' : currentColor}
- aria-label={colorLabel}
- showAlpha={false}
- swatches={euiPaletteColorBlind()}
- />
- );
-
- return (
-
- {disabledMessage ? (
-
- {renderColorPicker()}
-
- ) : (
- renderColorPicker()
- )}
-
- );
-}
-
-export function DimensionDataExtraEditor(
- props: VisualizationDimensionEditorProps & {
- paletteService: PaletteRegistry;
- }
-) {
- const currentLayer = props.state.layers.find((layer) => layer.layerId === props.layerId);
-
- if (!currentLayer) {
- return null;
- }
-
- return (
- <>
- {[...currentLayer.primaryGroups, ...(currentLayer.secondaryGroups ?? [])].includes(
- props.accessor
- ) && (
- {
- props.setState({
- ...props.state,
- layers: props.state.layers.map((layer) =>
- layer.layerId !== props.layerId
- ? layer
- : {
- ...layer,
- collapseFns: {
- ...layer.collapseFns,
- [props.accessor]: collapseFn,
- },
- }
- ),
- });
- }}
- />
- )}
- >
- );
-}
diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts
index 3a4b0545a42ea..3f1ce72fac7f1 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts
+++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts
@@ -411,6 +411,51 @@ describe('pie_visualization', () => {
`);
});
+ it("applies color swatch icons on multiple metrics if there's a collapsed slice-by", () => {
+ const palette = paletteServiceMock.get('default');
+ palette.getCategoricalColor.mockClear();
+ const state = getExampleState();
+ state.layers[0].allowMultipleMetrics = true;
+ state.layers[0].metrics = colIds;
+ state.layers[0].colorsByDimension = {};
+ state.layers[0].colorsByDimension[colIds[0]] = 'overridden-color';
+ state.layers[0].primaryGroups = ['primaryGroup'];
+ state.layers[0].collapseFns = { ['primaryGroup']: 'sum' };
+
+ const config = pieVisualization.getConfiguration({
+ state,
+ frame,
+ layerId: state.layers[0].layerId,
+ });
+
+ expect(findMetricGroup(config)?.accessors).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "color": "overridden-color",
+ "columnId": "1",
+ "triggerIconType": "color",
+ },
+ Object {
+ "color": "black",
+ "columnId": "2",
+ "triggerIconType": "color",
+ },
+ Object {
+ "color": "black",
+ "columnId": "3",
+ "triggerIconType": "color",
+ },
+ Object {
+ "color": "black",
+ "columnId": "4",
+ "triggerIconType": "color",
+ },
+ ]
+ `);
+
+ expect(palette.getCategoricalColor).toHaveBeenCalledTimes(3); // one for each of the defaultly assigned colors
+ });
+
it("applies disabled icons on multiple metrics if there's a slice-by", () => {
const state = getExampleState();
state.layers[0].allowMultipleMetrics = true;
diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
index 8d7ccc0ca7e6d..da4f275b49539 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
+++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx
@@ -42,7 +42,8 @@ import {
} from '../../../common/constants';
import { suggestions } from './suggestions';
import { PartitionChartsMeta } from './partition_charts_meta';
-import { DimensionDataExtraEditor, DimensionEditor, PieToolbar } from './toolbar';
+import { PieToolbar } from './toolbar';
+import { DimensionDataExtraEditor, DimensionEditor } from './dimension_editor';
import { LayerSettings } from './layer_settings';
import { checkTableForContainsSmallValues } from './render_helpers';
import { DatasourcePublicAPI } from '../..';
@@ -79,21 +80,29 @@ const numberMetricOperations = (op: OperationMetadata) =>
export const isCollapsed = (columnId: string, layer: PieLayerState) =>
Boolean(layer.collapseFns?.[columnId]);
+export const hasNonCollapsedSliceBy = (l: PieLayerState) => {
+ const sliceByLength = l.primaryGroups.length;
+ const collapsedGroupsLength =
+ (l.collapseFns && Object.values(l.collapseFns).filter(Boolean).length) ?? 0;
+ return sliceByLength - collapsedGroupsLength > 0;
+};
+
export const getDefaultColorForMultiMetricDimension = ({
layer,
columnId,
paletteService,
datasource,
+ palette,
}: {
layer: PieLayerState;
columnId: string;
paletteService: PaletteRegistry;
datasource: DatasourcePublicAPI | undefined;
+ palette?: PieVisualizationState['palette'];
}) => {
const columnToLabelMap = datasource ? getColumnToLabelMap(layer.metrics, datasource) : {};
const sortedMetrics = getSortedAccessorsForGroup(datasource, layer, 'metrics');
-
- return paletteService.get('default').getCategoricalColor([
+ return paletteService.get(palette?.name || 'default').getCategoricalColor([
{
name: columnToLabelMap[columnId],
rankAtDepth: sortedMetrics.indexOf(columnId),
@@ -320,8 +329,6 @@ export const getPieVisualization = ({
};
const getMetricGroupConfig = (): VisualizationDimensionGroupConfig => {
- const hasSliceBy = layer.primaryGroups.length + (layer.secondaryGroups?.length ?? 0);
-
const accessors: AccessorConfig[] = getSortedAccessorsForGroup(
datasource,
layer,
@@ -329,7 +336,7 @@ export const getPieVisualization = ({
).map((columnId) => ({
columnId,
...(layer.allowMultipleMetrics
- ? hasSliceBy
+ ? hasNonCollapsedSliceBy(layer)
? {
triggerIconType: 'disabled',
}
@@ -342,6 +349,7 @@ export const getPieVisualization = ({
columnId,
paletteService,
datasource,
+ palette: state.palette,
}) ??
undefined,
}
@@ -671,10 +679,11 @@ export const getPieVisualization = ({
columnId,
paletteService,
datasource,
+ palette: state.palette,
})
)
);
- } else if (!layer.primaryGroups?.length) {
+ } else if (!hasNonCollapsedSliceBy(layer)) {
// This is a logic integrated in the renderer, here simulated
// In the particular case of no color assigned (as no sliceBy dimension defined)
// the color is generated on the fly from the default palette
@@ -733,7 +742,7 @@ export const getPieVisualization = ({
});
});
- if (layer.primaryGroups.some((id) => !isCollapsed(id, layer))) {
+ if (hasNonCollapsedSliceBy(layer)) {
palette.push(
...paletteService
.get(state.palette?.name || 'default')
diff --git a/x-pack/plugins/lens/public/visualizations/xy/state_helpers.ts b/x-pack/plugins/lens/public/visualizations/xy/state_helpers.ts
index eff8a972782c6..0ac7392c274ac 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/state_helpers.ts
+++ b/x-pack/plugins/lens/public/visualizations/xy/state_helpers.ts
@@ -83,7 +83,7 @@ export const getSeriesColor = (layer: XYLayerConfig, accessor: string) => {
if (isAnnotationsLayer(layer)) {
return layer?.annotations?.find((ann) => ann.id === accessor)?.color || null;
}
- if (isDataLayer(layer) && layer.splitAccessor) {
+ if (isDataLayer(layer) && layer.splitAccessor && !layer.collapseFn) {
return null;
}
return (
diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx
index 4ce681eedbd51..97ac10d4824f0 100644
--- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/dimension_editor.tsx
@@ -122,6 +122,12 @@ export function DataDimensionEditor(
}
const isHorizontal = isHorizontalChart(state.layers);
+ const disabledMessage = Boolean(!localLayer.collapseFn && localLayer.splitAccessor)
+ ? i18n.translate('xpack.lens.xyChart.colorPicker.tooltip.disabled', {
+ defaultMessage:
+ 'You are unable to apply custom colors to individual series when the layer includes a "Break down by" field.',
+ })
+ : undefined;
return (
<>
@@ -129,7 +135,7 @@ export function DataDimensionEditor(
{...props}
overwriteColor={overwriteColor}
defaultColor={assignedColor}
- disabled={Boolean(!localLayer.collapseFn && localLayer.splitAccessor)}
+ disabledMessage={disabledMessage}
setConfig={setConfig}
/>
diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx
index 622fb0a961183..c3bd0394e19cc 100644
--- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx
+++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx
@@ -55,7 +55,7 @@ export interface ThreadingParams {
deploymentId?: string;
}
-const THREADS_MAX_EXPONENT = 4;
+const THREADS_MAX_EXPONENT = 5;
/**
* Form for setting threading params.
diff --git a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/response_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/response_schema.ts
index 3d899efdd04bd..e76ba63cfa17d 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/response_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/response_schema.ts
@@ -20,6 +20,9 @@ export interface PrebuiltRulesStatusStats {
/** Number of installed prebuilt rules available for upgrade (stock + customized) */
num_prebuilt_rules_to_upgrade: number;
+ /** Total number of prebuilt rules available in package (including already installed) */
+ num_prebuilt_rules_total_in_package: number;
+
// In the future we could add more stats such as:
// - number of installed prebuilt rules which were deprecated
// - number of installed prebuilt rules which are not compatible with the current version of Kibana
diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/coverage_overview/request_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/coverage_overview/request_schema.ts
index 65870fe2a95ba..48c3e4679702a 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/coverage_overview/request_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/rules/coverage_overview/request_schema.ts
@@ -74,6 +74,8 @@ export const CoverageOverviewFilter = t.partial({
});
export type CoverageOverviewRequestBody = t.TypeOf;
-export const CoverageOverviewRequestBody = t.partial({
- filter: CoverageOverviewFilter,
-});
+export const CoverageOverviewRequestBody = t.exact(
+ t.partial({
+ filter: CoverageOverviewFilter,
+ })
+);
diff --git a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/urls.ts b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/urls.ts
index 4a4cae72cf04d..e1175713a3a70 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/urls.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/rule_management/api/urls.ts
@@ -8,3 +8,4 @@
import { INTERNAL_DETECTION_ENGINE_URL } from '../../../constants';
export const RULE_MANAGEMENT_FILTERS_URL = `${INTERNAL_DETECTION_ENGINE_URL}/rules/_rule_management_filters`;
+export const RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL = `${INTERNAL_DETECTION_ENGINE_URL}/rules/_coverage_overview`;
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts
index 1b60699e7c19f..d0baf3fbe2a7d 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/fleet_action_generator.ts
@@ -30,7 +30,7 @@ export class FleetActionGenerator extends BaseDataGenerator {
expiration: this.randomFutureDate(timeStamp),
type: 'INPUT_ACTION',
input_type: 'endpoint',
- agents: [this.seededUUIDv4()],
+ agents: overrides.agents ? overrides.agents : [this.seededUUIDv4()],
user_id: 'elastic',
data: {
command: this.randomResponseActionCommand(),
diff --git a/x-pack/plugins/security_solution/common/rule_fields.ts b/x-pack/plugins/security_solution/common/rule_fields.ts
new file mode 100644
index 0000000000000..5d72cd15a96ae
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/rule_fields.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const RULE_NAME_FIELD = 'alert.attributes.name';
+
+export const RULE_PARAMS_FIELDS = {
+ INDEX: 'alert.attributes.params.index',
+ TACTIC_ID: 'alert.attributes.params.threat.tactic.id',
+ TACTIC_NAME: 'alert.attributes.params.threat.tactic.name',
+ TECHNIQUE_ID: 'alert.attributes.params.threat.technique.id',
+ TECHNIQUE_NAME: 'alert.attributes.params.threat.technique.name',
+ SUBTECHNIQUE_ID: 'alert.attributes.params.threat.technique.subtechnique.id',
+ SUBTECHNIQUE_NAME: 'alert.attributes.params.threat.technique.subtechnique.name',
+} as const;
+
+export const ENABLED_FIELD = 'alert.attributes.enabled';
+export const TAGS_FIELD = 'alert.attributes.tags';
+export const PARAMS_TYPE_FIELD = 'alert.attributes.params.type';
+export const PARAMS_IMMUTABLE_FIELD = 'alert.attributes.params.immutable';
+export const LAST_RUN_OUTCOME_FIELD = 'alert.attributes.lastRun.outcome';
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.test.ts b/x-pack/plugins/security_solution/common/utils/kql.test.ts
similarity index 71%
rename from x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.test.ts
rename to x-pack/plugins/security_solution/common/utils/kql.test.ts
index 1533e0c02fa62..799dc51770a52 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.test.ts
+++ b/x-pack/plugins/security_solution/common/utils/kql.test.ts
@@ -5,11 +5,10 @@
* 2.0.
*/
-import type { FilterOptions } from './types';
-import { convertRulesFilterToKQL } from './utils';
+import { convertRulesFilterToKQL } from './kql';
describe('convertRulesFilterToKQL', () => {
- const filterOptions: FilterOptions = {
+ const filterOptions = {
filter: '',
showCustomRules: false,
showElasticRules: false,
@@ -34,7 +33,7 @@ describe('convertRulesFilterToKQL', () => {
const kql = convertRulesFilterToKQL({ ...filterOptions, filter: '" OR (foo: bar)' });
expect(kql).toBe(
- '(alert.attributes.name: "\\" OR (foo: bar)" OR alert.attributes.params.index: "\\" OR (foo: bar)" OR alert.attributes.params.threat.tactic.id: "\\" OR (foo: bar)" OR alert.attributes.params.threat.tactic.name: "\\" OR (foo: bar)" OR alert.attributes.params.threat.technique.id: "\\" OR (foo: bar)" OR alert.attributes.params.threat.technique.name: "\\" OR (foo: bar)" OR alert.attributes.params.threat.technique.subtechnique.id: "\\" OR (foo: bar)" OR alert.attributes.params.threat.technique.subtechnique.name: "\\" OR (foo: bar)")'
+ '(alert.attributes.name: "\\" \\OR \\(foo\\: bar\\)" OR alert.attributes.params.index: "\\" \\OR \\(foo\\: bar\\)" OR alert.attributes.params.threat.tactic.id: "\\" \\OR \\(foo\\: bar\\)" OR alert.attributes.params.threat.tactic.name: "\\" \\OR \\(foo\\: bar\\)" OR alert.attributes.params.threat.technique.id: "\\" \\OR \\(foo\\: bar\\)" OR alert.attributes.params.threat.technique.name: "\\" \\OR \\(foo\\: bar\\)" OR alert.attributes.params.threat.technique.subtechnique.id: "\\" \\OR \\(foo\\: bar\\)" OR alert.attributes.params.threat.technique.subtechnique.name: "\\" \\OR \\(foo\\: bar\\)")'
);
});
@@ -75,7 +74,7 @@ describe('convertRulesFilterToKQL', () => {
});
expect(kql).toBe(
- `alert.attributes.params.immutable: true AND alert.attributes.tags:("tag1" AND "tag2") AND (alert.attributes.name: "foo" OR alert.attributes.params.index: "foo" OR alert.attributes.params.threat.tactic.id: "foo" OR alert.attributes.params.threat.tactic.name: "foo" OR alert.attributes.params.threat.technique.id: "foo" OR alert.attributes.params.threat.technique.name: "foo" OR alert.attributes.params.threat.technique.subtechnique.id: "foo" OR alert.attributes.params.threat.technique.subtechnique.name: "foo")`
+ `(alert.attributes.name: "foo" OR alert.attributes.params.index: "foo" OR alert.attributes.params.threat.tactic.id: "foo" OR alert.attributes.params.threat.tactic.name: "foo" OR alert.attributes.params.threat.technique.id: "foo" OR alert.attributes.params.threat.technique.name: "foo" OR alert.attributes.params.threat.technique.subtechnique.id: "foo" OR alert.attributes.params.threat.technique.subtechnique.name: "foo") AND alert.attributes.params.immutable: true AND alert.attributes.tags:(\"tag1\" AND \"tag2\")`
);
});
diff --git a/x-pack/plugins/security_solution/common/utils/kql.ts b/x-pack/plugins/security_solution/common/utils/kql.ts
new file mode 100644
index 0000000000000..d96385e429cf6
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/utils/kql.ts
@@ -0,0 +1,115 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { escapeKuery } from '@kbn/es-query';
+import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
+import { RuleExecutionStatus } from '../detection_engine/rule_monitoring';
+import {
+ ENABLED_FIELD,
+ LAST_RUN_OUTCOME_FIELD,
+ PARAMS_IMMUTABLE_FIELD,
+ PARAMS_TYPE_FIELD,
+ RULE_NAME_FIELD,
+ RULE_PARAMS_FIELDS,
+ TAGS_FIELD,
+} from '../rule_fields';
+
+export const KQL_FILTER_IMMUTABLE_RULES = `${PARAMS_IMMUTABLE_FIELD}: true`;
+export const KQL_FILTER_MUTABLE_RULES = `${PARAMS_IMMUTABLE_FIELD}: false`;
+export const KQL_FILTER_ENABLED_RULES = `${ENABLED_FIELD}: true`;
+export const KQL_FILTER_DISABLED_RULES = `${ENABLED_FIELD}: false`;
+
+interface RulesFilterOptions {
+ filter: string;
+ showCustomRules: boolean;
+ showElasticRules: boolean;
+ enabled: boolean;
+ tags: string[];
+ excludeRuleTypes: Type[];
+ ruleExecutionStatus: RuleExecutionStatus;
+}
+
+/**
+ * Convert rules filter options object to KQL query
+ *
+ * @param filterOptions desired filters (e.g. filter/sortField/sortOrder)
+ *
+ * @returns KQL string
+ */
+export function convertRulesFilterToKQL({
+ filter: searchTerm,
+ showCustomRules,
+ showElasticRules,
+ enabled,
+ tags,
+ excludeRuleTypes = [],
+ ruleExecutionStatus,
+}: Partial): string {
+ const kql: string[] = [];
+
+ if (searchTerm?.length) {
+ kql.push(`(${convertRuleSearchTermToKQL(searchTerm)})`);
+ }
+
+ if (showCustomRules && showElasticRules) {
+ // if both showCustomRules && showElasticRules selected we omit filter, as it includes all existing rules
+ } else if (showElasticRules) {
+ kql.push(KQL_FILTER_IMMUTABLE_RULES);
+ } else if (showCustomRules) {
+ kql.push(KQL_FILTER_MUTABLE_RULES);
+ }
+
+ if (enabled !== undefined) {
+ kql.push(enabled ? KQL_FILTER_ENABLED_RULES : KQL_FILTER_DISABLED_RULES);
+ }
+
+ if (tags?.length) {
+ kql.push(convertRuleTagsToKQL(tags));
+ }
+
+ if (excludeRuleTypes.length) {
+ kql.push(`NOT ${convertRuleTypesToKQL(excludeRuleTypes)}`);
+ }
+
+ if (ruleExecutionStatus === RuleExecutionStatus.succeeded) {
+ kql.push(`${LAST_RUN_OUTCOME_FIELD}: "succeeded"`);
+ } else if (ruleExecutionStatus === RuleExecutionStatus['partial failure']) {
+ kql.push(`${LAST_RUN_OUTCOME_FIELD}: "warning"`);
+ } else if (ruleExecutionStatus === RuleExecutionStatus.failed) {
+ kql.push(`${LAST_RUN_OUTCOME_FIELD}: "failed"`);
+ }
+
+ return kql.join(' AND ');
+}
+
+const SEARCHABLE_RULE_ATTRIBUTES = [
+ RULE_NAME_FIELD,
+ RULE_PARAMS_FIELDS.INDEX,
+ RULE_PARAMS_FIELDS.TACTIC_ID,
+ RULE_PARAMS_FIELDS.TACTIC_NAME,
+ RULE_PARAMS_FIELDS.TECHNIQUE_ID,
+ RULE_PARAMS_FIELDS.TECHNIQUE_NAME,
+ RULE_PARAMS_FIELDS.SUBTECHNIQUE_ID,
+ RULE_PARAMS_FIELDS.SUBTECHNIQUE_NAME,
+];
+
+export function convertRuleSearchTermToKQL(
+ searchTerm: string,
+ attributes = SEARCHABLE_RULE_ATTRIBUTES
+): string {
+ return attributes.map((param) => `${param}: "${escapeKuery(searchTerm)}"`).join(' OR ');
+}
+
+export function convertRuleTagsToKQL(tags: string[]): string {
+ return `${TAGS_FIELD}:(${tags.map((tag) => `"${escapeKuery(tag)}"`).join(' AND ')})`;
+}
+
+export function convertRuleTypesToKQL(ruleTypes: Type[]): string {
+ return `${PARAMS_TYPE_FIELD}: (${ruleTypes
+ .map((ruleType) => `"${escapeKuery(ruleType)}"`)
+ .join(' OR ')})`;
+}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts
index 8ec7f77531653..72ee869e1a5f3 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.test.ts
@@ -184,7 +184,7 @@ describe('Detections Rules API', () => {
method: 'GET',
query: {
filter:
- '(alert.attributes.name: "\\" OR (foo:bar)" OR alert.attributes.params.index: "\\" OR (foo:bar)" OR alert.attributes.params.threat.tactic.id: "\\" OR (foo:bar)" OR alert.attributes.params.threat.tactic.name: "\\" OR (foo:bar)" OR alert.attributes.params.threat.technique.id: "\\" OR (foo:bar)" OR alert.attributes.params.threat.technique.name: "\\" OR (foo:bar)" OR alert.attributes.params.threat.technique.subtechnique.id: "\\" OR (foo:bar)" OR alert.attributes.params.threat.technique.subtechnique.name: "\\" OR (foo:bar)")',
+ '(alert.attributes.name: "\\" \\OR \\(foo\\:bar\\)" OR alert.attributes.params.index: "\\" \\OR \\(foo\\:bar\\)" OR alert.attributes.params.threat.tactic.id: "\\" \\OR \\(foo\\:bar\\)" OR alert.attributes.params.threat.tactic.name: "\\" \\OR \\(foo\\:bar\\)" OR alert.attributes.params.threat.technique.id: "\\" \\OR \\(foo\\:bar\\)" OR alert.attributes.params.threat.technique.name: "\\" \\OR \\(foo\\:bar\\)" OR alert.attributes.params.threat.technique.subtechnique.id: "\\" \\OR \\(foo\\:bar\\)" OR alert.attributes.params.threat.technique.subtechnique.name: "\\" \\OR \\(foo\\:bar\\)")',
page: 1,
per_page: 20,
sort_field: 'enabled',
@@ -395,7 +395,7 @@ describe('Detections Rules API', () => {
method: 'GET',
query: {
filter:
- 'alert.attributes.tags:("hello" AND "world") AND (alert.attributes.name: "ruleName" OR alert.attributes.params.index: "ruleName" OR alert.attributes.params.threat.tactic.id: "ruleName" OR alert.attributes.params.threat.tactic.name: "ruleName" OR alert.attributes.params.threat.technique.id: "ruleName" OR alert.attributes.params.threat.technique.name: "ruleName" OR alert.attributes.params.threat.technique.subtechnique.id: "ruleName" OR alert.attributes.params.threat.technique.subtechnique.name: "ruleName")',
+ '(alert.attributes.name: "ruleName" OR alert.attributes.params.index: "ruleName" OR alert.attributes.params.threat.tactic.id: "ruleName" OR alert.attributes.params.threat.tactic.name: "ruleName" OR alert.attributes.params.threat.technique.id: "ruleName" OR alert.attributes.params.threat.technique.name: "ruleName" OR alert.attributes.params.threat.technique.subtechnique.id: "ruleName" OR alert.attributes.params.threat.technique.subtechnique.name: "ruleName") AND alert.attributes.tags:("hello" AND "world")',
page: 1,
per_page: 20,
sort_field: 'enabled',
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts
index 796ac3513f67b..64a1b0c87c65c 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/api.ts
@@ -16,6 +16,7 @@ import type { ActionResult } from '@kbn/actions-plugin/server';
import type { BulkInstallPackagesResponse } from '@kbn/fleet-plugin/common';
import { epmRouteService } from '@kbn/fleet-plugin/common';
import type { InstallPackageResponse } from '@kbn/fleet-plugin/common/types';
+import { convertRulesFilterToKQL } from '../../../../common/utils/kql';
import type { UpgradeSpecificRulesRequest } from '../../../../common/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_request_schema';
import type { PerformRuleUpgradeResponseBody } from '../../../../common/detection_engine/prebuilt_rules/api/perform_rule_upgrade/perform_rule_upgrade_response_schema';
import type { InstallSpecificRulesRequest } from '../../../../common/detection_engine/prebuilt_rules/api/perform_rule_installation/perform_rule_installation_request_schema';
@@ -73,7 +74,6 @@ import type {
RulesSnoozeSettingsMap,
UpdateRulesProps,
} from '../logic/types';
-import { convertRulesFilterToKQL } from '../logic/utils';
import type { ReviewRuleUpgradeResponseBody } from '../../../../common/detection_engine/prebuilt_rules/api/review_rule_upgrade/response_schema';
import type { ReviewRuleInstallationResponseBody } from '../../../../common/detection_engine/prebuilt_rules/api/review_rule_installation/response_schema';
@@ -169,14 +169,14 @@ export const fetchRules = async ({
},
signal,
}: FetchRulesProps): Promise => {
- const filterString = convertRulesFilterToKQL(filterOptions);
+ const kql = convertRulesFilterToKQL(filterOptions);
const query = {
page: pagination.page,
per_page: pagination.perPage,
sort_field: sortingOptions.field,
sort_order: sortingOptions.order,
- ...(filterString !== '' ? { filter: filterString } : {}),
+ ...(kql !== '' ? { filter: kql } : {}),
};
return KibanaServices.get().http.fetch(DETECTION_ENGINE_RULES_URL_FIND, {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_install_fleet_packages_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_install_fleet_packages_mutation.ts
index dff2bef7b39e7..d47f4799fcc30 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_install_fleet_packages_mutation.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_bulk_install_fleet_packages_mutation.ts
@@ -11,7 +11,9 @@ import { useMutation } from '@tanstack/react-query';
import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../common/detection_engine/constants';
import type { BulkInstallFleetPackagesProps } from '../api';
import { bulkInstallFleetPackages } from '../api';
+import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_install_review_query';
import { useInvalidateFetchPrebuiltRulesStatusQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_status_query';
+import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query';
export const BULK_INSTALL_FLEET_PACKAGES_MUTATION_KEY = [
'POST',
@@ -22,6 +24,8 @@ export const useBulkInstallFleetPackagesMutation = (
options?: UseMutationOptions
) => {
const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery();
+ const invalidatePrebuiltRulesInstallReview = useInvalidateFetchPrebuiltRulesInstallReviewQuery();
+ const invalidatePrebuiltRulesUpdateReview = useInvalidateFetchPrebuiltRulesUpgradeReviewQuery();
return useMutation((props: BulkInstallFleetPackagesProps) => bulkInstallFleetPackages(props), {
...options,
@@ -34,6 +38,8 @@ export const useBulkInstallFleetPackagesMutation = (
if (rulesPackage && 'result' in rulesPackage && rulesPackage.result.status === 'installed') {
// The rules package was installed/updated, so invalidate the pre-packaged rules status query
invalidatePrePackagedRulesStatus();
+ invalidatePrebuiltRulesInstallReview();
+ invalidatePrebuiltRulesUpdateReview();
}
if (options?.onSettled) {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.ts
index 3ed308c860b3d..e4f88d8b9c3ef 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_install_fleet_package_mutation.ts
@@ -11,7 +11,9 @@ import { useMutation } from '@tanstack/react-query';
import { PREBUILT_RULES_PACKAGE_NAME } from '../../../../../common/detection_engine/constants';
import type { InstallFleetPackageProps } from '../api';
import { installFleetPackage } from '../api';
+import { useInvalidateFetchPrebuiltRulesInstallReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_install_review_query';
import { useInvalidateFetchPrebuiltRulesStatusQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_status_query';
+import { useInvalidateFetchPrebuiltRulesUpgradeReviewQuery } from './prebuilt_rules/use_fetch_prebuilt_rules_upgrade_review_query';
export const INSTALL_FLEET_PACKAGE_MUTATION_KEY = [
'POST',
@@ -22,6 +24,8 @@ export const useInstallFleetPackageMutation = (
options?: UseMutationOptions
) => {
const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery();
+ const invalidatePrebuiltRulesInstallReview = useInvalidateFetchPrebuiltRulesInstallReviewQuery();
+ const invalidatePrebuiltRulesUpdateReview = useInvalidateFetchPrebuiltRulesUpgradeReviewQuery();
return useMutation((props: InstallFleetPackageProps) => installFleetPackage(props), {
...options,
@@ -31,6 +35,8 @@ export const useInstallFleetPackageMutation = (
if (packageName === PREBUILT_RULES_PACKAGE_NAME) {
// Invalidate the pre-packaged rules status query as there might be new rules to install
invalidatePrePackagedRulesStatus();
+ invalidatePrebuiltRulesInstallReview();
+ invalidatePrebuiltRulesUpdateReview();
}
if (options?.onSettled) {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.ts
deleted file mode 100644
index d540c2d0f0b1f..0000000000000
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/utils.ts
+++ /dev/null
@@ -1,88 +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
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { escapeKuery } from '../../../common/lib/kuery';
-import type { FilterOptions } from './types';
-import { RuleExecutionStatus } from '../../../../common/detection_engine/rule_monitoring/model/execution_status';
-
-const SEARCHABLE_RULE_PARAMS = [
- 'alert.attributes.name',
- 'alert.attributes.params.index',
- 'alert.attributes.params.threat.tactic.id',
- 'alert.attributes.params.threat.tactic.name',
- 'alert.attributes.params.threat.technique.id',
- 'alert.attributes.params.threat.technique.name',
- 'alert.attributes.params.threat.technique.subtechnique.id',
- 'alert.attributes.params.threat.technique.subtechnique.name',
-];
-
-const ENABLED_FIELD = 'alert.attributes.enabled';
-const TAGS_FIELD = 'alert.attributes.tags';
-const PARAMS_TYPE_FIELD = 'alert.attributes.params.type';
-const PARAMS_IMMUTABLE_FIELD = 'alert.attributes.params.immutable';
-const LAST_RUN_OUTCOME_FIELD = 'alert.attributes.lastRun.outcome';
-
-/**
- * Convert rules filter options object to KQL query
- *
- * @param filterOptions desired filters (e.g. filter/sortField/sortOrder)
- *
- * @returns KQL string
- */
-export const convertRulesFilterToKQL = ({
- showCustomRules,
- showElasticRules,
- filter,
- tags,
- excludeRuleTypes = [],
- enabled,
- ruleExecutionStatus,
-}: FilterOptions): string => {
- const filters: string[] = [];
-
- if (showCustomRules && showElasticRules) {
- // if both showCustomRules && showElasticRules selected we omit filter, as it includes all existing rules
- } else if (showElasticRules) {
- filters.push(`${PARAMS_IMMUTABLE_FIELD}: true`);
- } else if (showCustomRules) {
- filters.push(`${PARAMS_IMMUTABLE_FIELD}: false`);
- }
-
- if (enabled !== undefined) {
- filters.push(`${ENABLED_FIELD}: ${enabled ? 'true' : 'false'}`);
- }
-
- if (tags.length > 0) {
- filters.push(`${TAGS_FIELD}:(${tags.map((tag) => `"${escapeKuery(tag)}"`).join(' AND ')})`);
- }
-
- if (filter.length) {
- const searchQuery = SEARCHABLE_RULE_PARAMS.map(
- (param) => `${param}: "${escapeKuery(filter)}"`
- ).join(' OR ');
-
- filters.push(`(${searchQuery})`);
- }
-
- if (excludeRuleTypes.length) {
- filters.push(
- `NOT ${PARAMS_TYPE_FIELD}: (${excludeRuleTypes
- .map((ruleType) => `"${escapeKuery(ruleType)}"`)
- .join(' OR ')})`
- );
- }
-
- if (ruleExecutionStatus === RuleExecutionStatus.succeeded) {
- filters.push(`${LAST_RUN_OUTCOME_FIELD}: "succeeded"`);
- } else if (ruleExecutionStatus === RuleExecutionStatus['partial failure']) {
- filters.push(`${LAST_RUN_OUTCOME_FIELD}: "warning"`);
- } else if (ruleExecutionStatus === RuleExecutionStatus.failed) {
- filters.push(`${LAST_RUN_OUTCOME_FIELD}: "failed"`);
- }
-
- return filters.join(' AND ');
-};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx
index ec9eb2f3fb2a9..b9fd334e9393c 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_header_buttons.tsx
@@ -12,7 +12,7 @@ import * as i18n from './translations';
export const AddPrebuiltRulesHeaderButtons = () => {
const {
- state: { rules, selectedRules, loadingRules },
+ state: { rules, selectedRules, loadingRules, isRefetching, isUpgradingSecurityPackages },
actions: { installAllRules, installSelectedRules },
} = useAddPrebuiltRulesTableContext();
@@ -21,12 +21,13 @@ export const AddPrebuiltRulesHeaderButtons = () => {
const shouldDisplayInstallSelectedRulesButton = numberOfSelectedRules > 0;
const isRuleInstalling = loadingRules.length > 0;
+ const isRequestInProgress = isRuleInstalling || isRefetching || isUpgradingSecurityPackages;
return (
{shouldDisplayInstallSelectedRulesButton ? (
-
+
{i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)}
{isRuleInstalling ? : undefined}
@@ -38,7 +39,7 @@ export const AddPrebuiltRulesHeaderButtons = () => {
iconType="plusInCircle"
data-test-subj="installAllRulesButton"
onClick={installAllRules}
- disabled={!isRulesAvailableForInstall || isRuleInstalling}
+ disabled={!isRulesAvailableForInstall || isRequestInProgress}
>
{i18n.INSTALL_ALL}
{isRuleInstalling ? : undefined}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx
index b131704bf8be7..7c8b397f1badd 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table.tsx
@@ -14,7 +14,6 @@ import {
} from '@elastic/eui';
import React from 'react';
-import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages';
import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants';
import { AddPrebuiltRulesTableNoItemsMessage } from './add_prebuilt_rules_no_items_message';
import { useAddPrebuiltRulesTableContext } from './add_prebuilt_rules_table_context';
@@ -25,24 +24,29 @@ import { useAddPrebuiltRulesTableColumns } from './use_add_prebuilt_rules_table_
* Table Component for displaying new rules that are available to be installed
*/
export const AddPrebuiltRulesTable = React.memo(() => {
- const isUpgradingSecurityPackages = useIsUpgradingSecurityPackages();
-
const addRulesTableContext = useAddPrebuiltRulesTableContext();
const {
- state: { rules, filteredRules, isFetched, isLoading, isRefetching, selectedRules },
+ state: {
+ rules,
+ filteredRules,
+ isFetched,
+ isLoading,
+ isRefetching,
+ selectedRules,
+ isUpgradingSecurityPackages,
+ },
actions: { selectRules },
} = addRulesTableContext;
const rulesColumns = useAddPrebuiltRulesTableColumns();
const isTableEmpty = isFetched && rules.length === 0;
- const shouldShowLinearProgress = (isFetched && isRefetching) || isUpgradingSecurityPackages;
- const shouldShowLoadingOverlay = !isFetched && isRefetching;
+ const shouldShowProgress = isUpgradingSecurityPackages || isRefetching;
return (
<>
- {shouldShowLinearProgress && (
+ {shouldShowProgress && (
{
/>
)}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx
index 6691eb7598070..b84134b3827da 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/add_prebuilt_rules_table_context.tsx
@@ -7,6 +7,8 @@
import type { Dispatch, SetStateAction } from 'react';
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
+import { useFetchPrebuiltRulesStatusQuery } from '../../../../rule_management/api/hooks/prebuilt_rules/use_fetch_prebuilt_rules_status_query';
+import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages';
import type { RuleInstallationInfoForReview } from '../../../../../../common/detection_engine/prebuilt_rules/api/review_rule_installation/response_schema';
import type { RuleSignatureId } from '../../../../../../common/detection_engine/rule_schema';
import { invariant } from '../../../../../../common/utils/invariant';
@@ -47,6 +49,11 @@ export interface AddPrebuiltRulesTableState {
* Is true whenever a background refetch is in-flight, which does not include initial loading
*/
isRefetching: boolean;
+ /**
+ * Is true when installing security_detection_rules
+ * package in background
+ */
+ isUpgradingSecurityPackages: boolean;
/**
* List of rule IDs that are currently being upgraded
*/
@@ -92,6 +99,10 @@ export const AddPrebuiltRulesTableContextProvider = ({
tags: [],
});
+ const { data: prebuiltRulesStatus } = useFetchPrebuiltRulesStatusQuery();
+
+ const isUpgradingSecurityPackages = useIsUpgradingSecurityPackages();
+
const {
data: { rules, stats: { tags } } = {
rules: [],
@@ -105,6 +116,12 @@ export const AddPrebuiltRulesTableContextProvider = ({
} = usePrebuiltRulesInstallReview({
refetchInterval: 60000, // Refetch available rules for installation every minute
keepPreviousData: true, // Use this option so that the state doesn't jump between "success" and "loading" on page change
+ // Fetch rules to install only after background installation of security_detection_rules package is complete
+ enabled: Boolean(
+ !isUpgradingSecurityPackages &&
+ prebuiltRulesStatus &&
+ prebuiltRulesStatus.num_prebuilt_rules_total_in_package > 0
+ ),
});
const { mutateAsync: installAllRulesRequest } = usePerformInstallAllRules();
@@ -175,6 +192,7 @@ export const AddPrebuiltRulesTableContextProvider = ({
isLoading,
loadingRules,
isRefetching,
+ isUpgradingSecurityPackages,
selectedRules,
lastUpdated: dataUpdatedAt,
},
@@ -189,6 +207,7 @@ export const AddPrebuiltRulesTableContextProvider = ({
isLoading,
loadingRules,
isRefetching,
+ isUpgradingSecurityPackages,
selectedRules,
dataUpdatedAt,
actions,
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx
index d21fd175ed60c..91acd8e01f1cd 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/add_prebuilt_rules_table/use_add_prebuilt_rules_table_columns.tsx
@@ -85,14 +85,20 @@ const INTEGRATIONS_COLUMN: TableColumn = {
const createInstallButtonColumn = (
installOneRule: AddPrebuiltRulesTableActions['installOneRule'],
- loadingRules: RuleSignatureId[]
+ loadingRules: RuleSignatureId[],
+ isDisabled: boolean
): TableColumn => ({
field: 'rule_id',
name: '',
render: (ruleId: RuleSignatureId) => {
const isRuleInstalling = loadingRules.includes(ruleId);
+ const isInstallButtonDisabled = isRuleInstalling || isDisabled;
return (
- installOneRule(ruleId)}>
+ installOneRule(ruleId)}
+ >
{isRuleInstalling ? : i18n.INSTALL_RULE_BUTTON}
);
@@ -106,10 +112,12 @@ export const useAddPrebuiltRulesTableColumns = (): TableColumn[] => {
const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD);
const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING);
const {
- state: { loadingRules },
+ state: { loadingRules, isRefetching, isUpgradingSecurityPackages },
actions: { installOneRule },
} = useAddPrebuiltRulesTableContext();
+ const isDisabled = isRefetching || isUpgradingSecurityPackages;
+
return useMemo(
() => [
RULE_NAME_COLUMN,
@@ -135,8 +143,10 @@ export const useAddPrebuiltRulesTableColumns = (): TableColumn[] => {
truncateText: true,
width: '12%',
},
- ...(hasCRUDPermissions ? [createInstallButtonColumn(installOneRule, loadingRules)] : []),
+ ...(hasCRUDPermissions
+ ? [createInstallButtonColumn(installOneRule, loadingRules, isDisabled)]
+ : []),
],
- [hasCRUDPermissions, installOneRule, loadingRules, showRelatedIntegrations]
+ [hasCRUDPermissions, installOneRule, loadingRules, isDisabled, showRelatedIntegrations]
);
};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx
index b1804e5c29f0a..3b7cbe1efad11 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx
@@ -12,6 +12,7 @@ import type { Toast } from '@kbn/core/public';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { euiThemeVars } from '@kbn/ui-theme';
import React, { useCallback } from 'react';
+import { convertRulesFilterToKQL } from '../../../../../../common/utils/kql';
import { DuplicateOptions } from '../../../../../../common/detection_engine/rule_management/constants';
import type { BulkActionEditPayload } from '../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
import {
@@ -29,7 +30,6 @@ import { useBulkExport } from '../../../../rule_management/logic/bulk_actions/us
import { useExecuteBulkAction } from '../../../../rule_management/logic/bulk_actions/use_execute_bulk_action';
import { useDownloadExportedRules } from '../../../../rule_management/logic/bulk_actions/use_download_exported_rules';
import type { FilterOptions } from '../../../../rule_management/logic/types';
-import { convertRulesFilterToKQL } from '../../../../rule_management/logic/utils';
import { getExportedRulesDetails } from '../helpers';
import { useRulesTableContext } from '../rules_table/rules_table_context';
import { useHasActionsPrivileges } from '../use_has_actions_privileges';
@@ -66,7 +66,7 @@ export const useBulkActions = ({
const rulesTableContext = useRulesTableContext();
const hasActionsPrivileges = useHasActionsPrivileges();
const toasts = useAppToasts();
- const filterQuery = convertRulesFilterToKQL(filterOptions);
+ const kql = convertRulesFilterToKQL(filterOptions);
const { startTransaction } = useStartTransaction();
const { executeBulkAction } = useExecuteBulkAction();
const { bulkExport } = useBulkExport();
@@ -108,7 +108,7 @@ export const useBulkActions = ({
await executeBulkAction({
type: BulkActionType.enable,
- ...(isAllSelected ? { query: filterQuery } : { ids: ruleIds }),
+ ...(isAllSelected ? { query: kql } : { ids: ruleIds }),
});
};
@@ -120,7 +120,7 @@ export const useBulkActions = ({
await executeBulkAction({
type: BulkActionType.disable,
- ...(isAllSelected ? { query: filterQuery } : { ids: enabledIds }),
+ ...(isAllSelected ? { query: kql } : { ids: enabledIds }),
});
};
@@ -144,7 +144,7 @@ export const useBulkActions = ({
DuplicateOptions.withExceptionsExcludeExpiredExceptions
),
},
- ...(isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }),
+ ...(isAllSelected ? { query: kql } : { ids: selectedRuleIds }),
});
clearRulesSelection();
};
@@ -163,7 +163,7 @@ export const useBulkActions = ({
await executeBulkAction({
type: BulkActionType.delete,
- ...(isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }),
+ ...(isAllSelected ? { query: kql } : { ids: selectedRuleIds }),
});
};
@@ -172,7 +172,7 @@ export const useBulkActions = ({
startTransaction({ name: BULK_RULE_ACTIONS.EXPORT });
const response = await bulkExport(
- isAllSelected ? { query: filterQuery } : { ids: selectedRuleIds }
+ isAllSelected ? { query: kql } : { ids: selectedRuleIds }
);
// if response null, likely network error happened and export rules haven't been received
@@ -472,7 +472,7 @@ export const useBulkActions = ({
startTransaction,
hasMlPermissions,
executeBulkAction,
- filterQuery,
+ kql,
toasts,
showBulkDuplicateConfirmation,
clearRulesSelection,
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts
index 4a88d19e53970..cc7524234feef 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.test.ts
@@ -8,12 +8,12 @@
import type { DryRunResult } from '../types';
import type { FilterOptions } from '../../../../../rule_management/logic/types';
-import { convertRulesFilterToKQL } from '../../../../../rule_management/logic/utils';
import { BulkActionsDryRunErrCode } from '../../../../../../../common/constants';
import { prepareSearchParams } from './prepare_search_params';
+import { convertRulesFilterToKQL } from '../../../../../../../common/utils/kql';
-jest.mock('../../../../../rule_management/logic/utils', () => ({
+jest.mock('../../../../../../../common/utils/kql', () => ({
convertRulesFilterToKQL: jest.fn().mockReturnValue('str'),
}));
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts
index 9b0986d1d68d3..c868a0b34270b 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/prepare_search_params.ts
@@ -5,11 +5,11 @@
* 2.0.
*/
+import { convertRulesFilterToKQL } from '../../../../../../../common/utils/kql';
import type { QueryOrIds } from '../../../../../rule_management/logic';
import type { DryRunResult } from '../types';
import type { FilterOptions } from '../../../../../rule_management/logic/types';
-import { convertRulesFilterToKQL } from '../../../../../rule_management/logic/utils';
import { BulkActionsDryRunErrCode } from '../../../../../../../common/constants';
type PrepareSearchFilterProps =
@@ -52,5 +52,7 @@ export const prepareSearchParams = ({
}
});
- return { query: convertRulesFilterToKQL(modifiedFilterOptions) };
+ return {
+ query: convertRulesFilterToKQL(modifiedFilterOptions),
+ };
};
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx
index e6afcb08c16bb..b7848eb5379e7 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table.tsx
@@ -17,7 +17,6 @@ import {
} from '@elastic/eui';
import React from 'react';
import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations';
-import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages';
import { RULES_TABLE_INITIAL_PAGE_SIZE, RULES_TABLE_PAGE_SIZE_OPTIONS } from '../constants';
import { UpgradePrebuiltRulesTableButtons } from './upgrade_prebuilt_rules_table_buttons';
import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_table_context';
@@ -36,24 +35,29 @@ const NO_ITEMS_MESSAGE = (
* Table Component for displaying rules that have available updates
*/
export const UpgradePrebuiltRulesTable = React.memo(() => {
- const isUpgradingSecurityPackages = useIsUpgradingSecurityPackages();
-
const upgradeRulesTableContext = useUpgradePrebuiltRulesTableContext();
const {
- state: { rules, filteredRules, isFetched, isLoading, isRefetching, selectedRules },
+ state: {
+ rules,
+ filteredRules,
+ isFetched,
+ isLoading,
+ selectedRules,
+ isRefetching,
+ isUpgradingSecurityPackages,
+ },
actions: { selectRules },
} = upgradeRulesTableContext;
const rulesColumns = useUpgradePrebuiltRulesTableColumns();
const isTableEmpty = isFetched && rules.length === 0;
- const shouldShowLinearProgress = (isFetched && isRefetching) || isUpgradingSecurityPackages;
- const shouldShowLoadingOverlay = !isFetched && isRefetching;
+ const shouldShowProgress = isUpgradingSecurityPackages || isRefetching;
return (
<>
- {shouldShowLinearProgress && (
+ {shouldShowProgress && (
{
/>
)}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx
index 640fb5a74f9cf..5dfff6c5fc462 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_buttons.tsx
@@ -12,7 +12,7 @@ import { useUpgradePrebuiltRulesTableContext } from './upgrade_prebuilt_rules_ta
export const UpgradePrebuiltRulesTableButtons = () => {
const {
- state: { rules, selectedRules, loadingRules },
+ state: { rules, selectedRules, loadingRules, isRefetching, isUpgradingSecurityPackages },
actions: { upgradeAllRules, upgradeSelectedRules },
} = useUpgradePrebuiltRulesTableContext();
@@ -21,12 +21,13 @@ export const UpgradePrebuiltRulesTableButtons = () => {
const shouldDisplayUpgradeSelectedRulesButton = numberOfSelectedRules > 0;
const isRuleUpgrading = loadingRules.length > 0;
+ const isRequestInProgress = isRuleUpgrading || isRefetching || isUpgradingSecurityPackages;
return (
{shouldDisplayUpgradeSelectedRulesButton ? (
-
+
<>
{i18n.UPDATE_SELECTED_RULES(numberOfSelectedRules)}
{isRuleUpgrading ? : undefined}
@@ -39,7 +40,7 @@ export const UpgradePrebuiltRulesTableButtons = () => {
fill
iconType="plusInCircle"
onClick={upgradeAllRules}
- disabled={!isRulesAvailableForUpgrade || isRuleUpgrading}
+ disabled={!isRulesAvailableForUpgrade || isRequestInProgress}
>
{i18n.UPDATE_ALL}
{isRuleUpgrading ? : undefined}
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx
index 1e4af49c53254..ce4d99d440e82 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx
@@ -7,6 +7,7 @@
import type { Dispatch, SetStateAction } from 'react';
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
+import { useIsUpgradingSecurityPackages } from '../../../../rule_management/logic/use_upgrade_security_packages';
import { useInstalledSecurityJobs } from '../../../../../common/components/ml/hooks/use_installed_security_jobs';
import { useBoolState } from '../../../../../common/hooks/use_bool_state';
import { affectedJobIds } from '../../../../../detections/components/callouts/ml_job_compatibility_callout/affected_job_ids';
@@ -53,6 +54,11 @@ export interface UpgradePrebuiltRulesTableState {
* Is true whenever a background refetch is in-flight, which does not include initial loading
*/
isRefetching: boolean;
+ /**
+ * Is true when installing security_detection_rules
+ * package in background
+ */
+ isUpgradingSecurityPackages: boolean;
/**
* List of rule IDs that are currently being upgraded
*/
@@ -100,6 +106,8 @@ export const UpgradePrebuiltRulesTableContextProvider = ({
tags: [],
});
+ const isUpgradingSecurityPackages = useIsUpgradingSecurityPackages();
+
const {
data: { rules, stats: { tags } } = {
rules: [],
@@ -211,11 +219,10 @@ export const UpgradePrebuiltRulesTableContextProvider = ({
isFetched,
isLoading: isLoading && loadingJobs,
isRefetching,
+ isUpgradingSecurityPackages,
selectedRules,
loadingRules,
lastUpdated: dataUpdatedAt,
- legacyJobsInstalled,
- isUpgradeModalVisible,
},
actions,
};
@@ -228,11 +235,10 @@ export const UpgradePrebuiltRulesTableContextProvider = ({
isLoading,
loadingJobs,
isRefetching,
+ isUpgradingSecurityPackages,
selectedRules,
loadingRules,
dataUpdatedAt,
- legacyJobsInstalled,
- isUpgradeModalVisible,
actions,
]);
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx
index 603d06c821fa6..9434ae72b8b08 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/use_upgrade_prebuilt_rules_table_columns.tsx
@@ -85,14 +85,20 @@ const INTEGRATIONS_COLUMN: TableColumn = {
const createUpgradeButtonColumn = (
upgradeOneRule: UpgradePrebuiltRulesTableActions['upgradeOneRule'],
- loadingRules: RuleSignatureId[]
+ loadingRules: RuleSignatureId[],
+ isDisabled: boolean
): TableColumn => ({
field: 'rule_id',
name: '',
render: (ruleId: RuleUpgradeInfoForReview['rule_id']) => {
const isRuleUpgrading = loadingRules.includes(ruleId);
+ const isUpgradeButtonDisabled = isRuleUpgrading || isDisabled;
return (
- upgradeOneRule(ruleId)}>
+ upgradeOneRule(ruleId)}
+ >
{isRuleUpgrading ? : i18n.UPDATE_RULE_BUTTON}
);
@@ -106,10 +112,12 @@ export const useUpgradePrebuiltRulesTableColumns = (): TableColumn[] => {
const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD);
const [showRelatedIntegrations] = useUiSetting$(SHOW_RELATED_INTEGRATIONS_SETTING);
const {
- state: { loadingRules },
+ state: { loadingRules, isRefetching, isUpgradingSecurityPackages },
actions: { upgradeOneRule },
} = useUpgradePrebuiltRulesTableContext();
+ const isDisabled = isRefetching || isUpgradingSecurityPackages;
+
return useMemo(
() => [
RULE_NAME_COLUMN,
@@ -136,8 +144,10 @@ export const useUpgradePrebuiltRulesTableColumns = (): TableColumn[] => {
truncateText: true,
width: '12%',
},
- ...(hasCRUDPermissions ? [createUpgradeButtonColumn(upgradeOneRule, loadingRules)] : []),
+ ...(hasCRUDPermissions
+ ? [createUpgradeButtonColumn(upgradeOneRule, loadingRules, isDisabled)]
+ : []),
],
- [hasCRUDPermissions, loadingRules, showRelatedIntegrations, upgradeOneRule]
+ [hasCRUDPermissions, loadingRules, isDisabled, showRelatedIntegrations, upgradeOneRule]
);
};
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx
index a1b14f31bf57c..74ad9e92fec07 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/actions_log_table.tsx
@@ -212,11 +212,12 @@ const getResponseActionListTableColumns = ({
},
},
{
+ field: 'status',
name: TABLE_COLUMN_NAMES.status,
width: !showHostNames ? '15%' : '10%',
- render: (action: ActionListApiResponse['data'][number]) => {
- const _status = action.errors?.length ? 'failed' : action.status;
+ render: (_status: ActionListApiResponse['data'][number]['status']) => {
const status = getActionStatus(_status);
+
return (
{
maxTimelineImportPayloadBytes: 10485760,
enableExperimental,
packagerTaskInterval: '60s',
+ packagerTaskPackagePolicyUpdateBatchSize: 10,
prebuiltRulesPackageVersion: '',
alertMergeStrategy: 'missingFields',
alertIgnoreFields: [],
diff --git a/x-pack/plugins/security_solution/server/config.ts b/x-pack/plugins/security_solution/server/config.ts
index dc732061ab947..ce177538f4f0b 100644
--- a/x-pack/plugins/security_solution/server/config.ts
+++ b/x-pack/plugins/security_solution/server/config.ts
@@ -95,6 +95,11 @@ export const configSchema = schema.object({
*/
packagerTaskInterval: schema.string({ defaultValue: '60s' }),
+ /**
+ * Artifacts Configuration for package policy update concurrency
+ */
+ packagerTaskPackagePolicyUpdateBatchSize: schema.number({ defaultValue: 10, max: 50, min: 1 }),
+
/**
* For internal use. Specify which version of the Detection Rules fleet package to install
* when upgrading rules. If not provided, the latest compatible package will be installed,
diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts
index 3ef1522109d35..bb46bc2da92b4 100644
--- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.test.ts
@@ -10,7 +10,12 @@ import { listMock } from '@kbn/lists-plugin/server/mocks';
import { getFoundExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/found_exception_list_item_schema.mock';
import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock';
import type { EntriesArray, EntryList } from '@kbn/securitysolution-io-ts-list-types';
-import { buildArtifact, getEndpointExceptionList, getFilteredEndpointExceptionList } from './lists';
+import {
+ buildArtifact,
+ getAllItemsFromEndpointExceptionList,
+ getFilteredEndpointExceptionListRaw,
+ convertExceptionsToEndpointFormat,
+} from './lists';
import type { TranslatedEntry, TranslatedExceptionListItem } from '../../schemas/artifacts';
import { ArtifactConstants } from './common';
import {
@@ -29,10 +34,10 @@ describe('artifacts lists', () => {
mockExceptionClient = listMock.getExceptionListClient();
});
- describe('getFilteredEndpointExceptionList', () => {
+ describe('getFilteredEndpointExceptionListRaw', () => {
const TEST_FILTER = 'exception-list-agnostic.attributes.os_types:"linux"';
- test('it should convert the exception lists response to the proper endpoint format', async () => {
+ test('it should get convert the exception lists response to the proper endpoint format', async () => {
const expectedEndpointExceptions = {
type: 'simple',
entries: [
@@ -59,13 +64,13 @@ describe('artifacts lists', () => {
const first = getFoundExceptionListItemSchemaMock();
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: TEST_FILTER,
listId: ENDPOINT_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -105,13 +110,13 @@ describe('artifacts lists', () => {
first.data[0].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: TEST_FILTER,
listId: ENDPOINT_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -156,13 +161,13 @@ describe('artifacts lists', () => {
first.data[0].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: TEST_FILTER,
listId: ENDPOINT_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -209,13 +214,13 @@ describe('artifacts lists', () => {
first.data[0].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: TEST_FILTER,
listId: ENDPOINT_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -261,13 +266,13 @@ describe('artifacts lists', () => {
first.data[0].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: TEST_FILTER,
listId: ENDPOINT_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -304,13 +309,13 @@ describe('artifacts lists', () => {
first.data[1].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: TEST_FILTER,
listId: ENDPOINT_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -347,13 +352,13 @@ describe('artifacts lists', () => {
first.data[0].entries = testEntries;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: TEST_FILTER,
listId: ENDPOINT_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -376,15 +381,15 @@ describe('artifacts lists', () => {
.mockReturnValueOnce(first)
.mockReturnValueOnce(second);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: TEST_FILTER,
listId: ENDPOINT_LIST_ID,
});
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
- // Expect 2 exceptions, the first two calls returned the same exception list items
- expect(resp.entries.length).toEqual(2);
+ // Expect 1 exceptions, the first two calls returned the same exception list items
+ expect(translated.entries.length).toEqual(1);
});
test('it should handle no exceptions', async () => {
@@ -392,13 +397,13 @@ describe('artifacts lists', () => {
exceptionsResponse.data = [];
exceptionsResponse.total = 0;
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(exceptionsResponse);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: TEST_FILTER,
listId: ENDPOINT_LIST_ID,
});
- expect(resp.entries.length).toEqual(0);
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated.entries.length).toEqual(0);
});
test('it should return a stable hash regardless of order of entries', async () => {
@@ -552,13 +557,13 @@ describe('artifacts lists', () => {
first.data[0].os_types = [os];
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -597,13 +602,13 @@ describe('artifacts lists', () => {
first.data[0].os_types = [os];
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -636,13 +641,13 @@ describe('artifacts lists', () => {
first.data[0].os_types = [os];
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -700,14 +705,14 @@ describe('artifacts lists', () => {
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
- expect(resp).toEqual({
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -740,13 +745,13 @@ describe('artifacts lists', () => {
first.data[0].os_types = [os];
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -804,14 +809,14 @@ describe('artifacts lists', () => {
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -854,13 +859,13 @@ describe('artifacts lists', () => {
first.data[0].os_types = [os];
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -901,13 +906,13 @@ describe('artifacts lists', () => {
first.data[0].os_types = [os];
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -942,13 +947,13 @@ describe('artifacts lists', () => {
first.data[0].os_types = [os];
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -1008,14 +1013,14 @@ describe('artifacts lists', () => {
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -1049,13 +1054,13 @@ describe('artifacts lists', () => {
first.data[0].os_types = [os];
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -1115,14 +1120,14 @@ describe('artifacts lists', () => {
mockExceptionClient.findExceptionListItem = jest.fn().mockReturnValueOnce(first);
- const resp = await getFilteredEndpointExceptionList({
+ const resp = await getFilteredEndpointExceptionListRaw({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
filter: `${getOsFilter(os)} and (exception-list-agnostic.attributes.tags:"policy:all")`,
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
- expect(resp).toEqual({
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual({
entries: [expectedEndpointExceptions],
});
});
@@ -1157,25 +1162,26 @@ describe('artifacts lists', () => {
],
};
- describe('Builds proper kuery without policy', () => {
+ describe('Builds proper kuery', () => {
test('for Endpoint List', async () => {
mockExceptionClient.findExceptionListItem = jest
.fn()
.mockReturnValueOnce(getFoundExceptionListItemSchemaMock());
- const resp = await getEndpointExceptionList({
+ const resp = await getAllItemsFromEndpointExceptionList({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
os: 'windows',
+ listId: ENDPOINT_LIST_ID,
});
- expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM);
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM);
expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({
listId: ENDPOINT_LIST_ID,
namespaceType: 'agnostic',
filter: 'exception-list-agnostic.attributes.os_types:"windows"',
- perPage: 100,
+ perPage: 1000,
page: 1,
sortField: 'created_at',
sortOrder: 'desc',
@@ -1187,21 +1193,20 @@ describe('artifacts lists', () => {
.fn()
.mockReturnValueOnce(getFoundExceptionListItemSchemaMock());
- const resp = await getEndpointExceptionList({
+ const resp = await getAllItemsFromEndpointExceptionList({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
os: 'macos',
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
});
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
- expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM);
+ expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM);
expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
namespaceType: 'agnostic',
- filter:
- 'exception-list-agnostic.attributes.os_types:"macos" and (exception-list-agnostic.attributes.tags:"policy:all")',
- perPage: 100,
+ filter: 'exception-list-agnostic.attributes.os_types:"macos"',
+ perPage: 1000,
page: 1,
sortField: 'created_at',
sortOrder: 'desc',
@@ -1213,21 +1218,20 @@ describe('artifacts lists', () => {
.fn()
.mockReturnValueOnce(getFoundExceptionListItemSchemaMock());
- const resp = await getEndpointExceptionList({
+ const resp = await getAllItemsFromEndpointExceptionList({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
os: 'macos',
listId: ENDPOINT_EVENT_FILTERS_LIST_ID,
});
- expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM);
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM);
expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({
listId: ENDPOINT_EVENT_FILTERS_LIST_ID,
namespaceType: 'agnostic',
- filter:
- 'exception-list-agnostic.attributes.os_types:"macos" and (exception-list-agnostic.attributes.tags:"policy:all")',
- perPage: 100,
+ filter: 'exception-list-agnostic.attributes.os_types:"macos"',
+ perPage: 1000,
page: 1,
sortField: 'created_at',
sortOrder: 'desc',
@@ -1239,164 +1243,45 @@ describe('artifacts lists', () => {
.fn()
.mockReturnValueOnce(getFoundExceptionListItemSchemaMock());
- const resp = await getEndpointExceptionList({
+ const resp = await getAllItemsFromEndpointExceptionList({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
os: 'macos',
listId: ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
});
- expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM);
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM);
expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({
listId: ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
namespaceType: 'agnostic',
- filter:
- 'exception-list-agnostic.attributes.os_types:"macos" and (exception-list-agnostic.attributes.tags:"policy:all")',
- perPage: 100,
- page: 1,
- sortField: 'created_at',
- sortOrder: 'desc',
- });
- });
-
- test('for Blocklists', async () => {
- mockExceptionClient.findExceptionListItem = jest
- .fn()
- .mockReturnValueOnce(getFoundExceptionListItemSchemaMock());
-
- const resp = await getEndpointExceptionList({
- elClient: mockExceptionClient,
- schemaVersion: 'v1',
- os: 'macos',
- listId: ENDPOINT_BLOCKLISTS_LIST_ID,
- });
-
- expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM);
-
- expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({
- listId: ENDPOINT_BLOCKLISTS_LIST_ID,
- namespaceType: 'agnostic',
- filter:
- 'exception-list-agnostic.attributes.os_types:"macos" and (exception-list-agnostic.attributes.tags:"policy:all")',
- perPage: 100,
- page: 1,
- sortField: 'created_at',
- sortOrder: 'desc',
- });
- });
- });
-
- describe('Build proper kuery with policy', () => {
- test('for Trusted Apps', async () => {
- mockExceptionClient.findExceptionListItem = jest
- .fn()
- .mockReturnValueOnce(getFoundExceptionListItemSchemaMock());
-
- const resp = await getEndpointExceptionList({
- elClient: mockExceptionClient,
- schemaVersion: 'v1',
- os: 'macos',
- policyId: 'c6d16e42-c32d-4dce-8a88-113cfe276ad1',
- listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
- });
-
- expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM);
-
- expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({
- listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
- namespaceType: 'agnostic',
- filter:
- 'exception-list-agnostic.attributes.os_types:"macos" and ' +
- '(exception-list-agnostic.attributes.tags:"policy:all" or ' +
- 'exception-list-agnostic.attributes.tags:"policy:c6d16e42-c32d-4dce-8a88-113cfe276ad1")',
- perPage: 100,
+ filter: 'exception-list-agnostic.attributes.os_types:"macos"',
+ perPage: 1000,
page: 1,
sortField: 'created_at',
sortOrder: 'desc',
});
});
- test('for Event Filters', async () => {
- mockExceptionClient.findExceptionListItem = jest
- .fn()
- .mockReturnValueOnce(getFoundExceptionListItemSchemaMock());
-
- const resp = await getEndpointExceptionList({
- elClient: mockExceptionClient,
- schemaVersion: 'v1',
- os: 'macos',
- policyId: 'c6d16e42-c32d-4dce-8a88-113cfe276ad1',
- listId: ENDPOINT_EVENT_FILTERS_LIST_ID,
- });
-
- expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM);
-
- expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({
- listId: ENDPOINT_EVENT_FILTERS_LIST_ID,
- namespaceType: 'agnostic',
- filter:
- 'exception-list-agnostic.attributes.os_types:"macos" and ' +
- '(exception-list-agnostic.attributes.tags:"policy:all" or ' +
- 'exception-list-agnostic.attributes.tags:"policy:c6d16e42-c32d-4dce-8a88-113cfe276ad1")',
- perPage: 100,
- page: 1,
- sortField: 'created_at',
- sortOrder: 'desc',
- });
- });
-
- test('for Host Isolation Exceptions', async () => {
- mockExceptionClient.findExceptionListItem = jest
- .fn()
- .mockReturnValueOnce(getFoundExceptionListItemSchemaMock());
-
- const resp = await getEndpointExceptionList({
- elClient: mockExceptionClient,
- schemaVersion: 'v1',
- os: 'macos',
- policyId: 'c6d16e42-c32d-4dce-8a88-113cfe276ad1',
- listId: ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
- });
-
- expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM);
-
- expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({
- listId: ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
- namespaceType: 'agnostic',
- filter:
- 'exception-list-agnostic.attributes.os_types:"macos" and ' +
- '(exception-list-agnostic.attributes.tags:"policy:all" or ' +
- 'exception-list-agnostic.attributes.tags:"policy:c6d16e42-c32d-4dce-8a88-113cfe276ad1")',
- perPage: 100,
- page: 1,
- sortField: 'created_at',
- sortOrder: 'desc',
- });
- });
test('for Blocklists', async () => {
mockExceptionClient.findExceptionListItem = jest
.fn()
.mockReturnValueOnce(getFoundExceptionListItemSchemaMock());
- const resp = await getEndpointExceptionList({
+ const resp = await getAllItemsFromEndpointExceptionList({
elClient: mockExceptionClient,
- schemaVersion: 'v1',
os: 'macos',
- policyId: 'c6d16e42-c32d-4dce-8a88-113cfe276ad1',
listId: ENDPOINT_BLOCKLISTS_LIST_ID,
});
- expect(resp).toEqual(TEST_EXCEPTION_LIST_ITEM);
+ const translated = convertExceptionsToEndpointFormat(resp, 'v1');
+ expect(translated).toEqual(TEST_EXCEPTION_LIST_ITEM);
expect(mockExceptionClient.findExceptionListItem).toHaveBeenCalledWith({
listId: ENDPOINT_BLOCKLISTS_LIST_ID,
namespaceType: 'agnostic',
- filter:
- 'exception-list-agnostic.attributes.os_types:"macos" and ' +
- '(exception-list-agnostic.attributes.tags:"policy:all" or ' +
- 'exception-list-agnostic.attributes.tags:"policy:c6d16e42-c32d-4dce-8a88-113cfe276ad1")',
- perPage: 100,
+ filter: 'exception-list-agnostic.attributes.os_types:"macos"',
+ perPage: 1000,
page: 1,
sortField: 'created_at',
sortOrder: 'desc',
diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts
index 581b46deebcfd..f8fe9d6e71453 100644
--- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts
@@ -11,8 +11,8 @@ import type {
Entry,
EntryNested,
ExceptionListItemSchema,
+ FoundExceptionListItemSchema,
} from '@kbn/securitysolution-io-ts-list-types';
-import { validate } from '@kbn/securitysolution-io-ts-utils';
import type { OperatingSystem } from '@kbn/securitysolution-utils';
import { hasSimpleExecutableName } from '@kbn/securitysolution-utils';
@@ -20,10 +20,11 @@ import type {
ENDPOINT_BLOCKLISTS_LIST_ID,
ENDPOINT_EVENT_FILTERS_LIST_ID,
ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
+ ENDPOINT_LIST_ID,
ENDPOINT_TRUSTED_APPS_LIST_ID,
} from '@kbn/securitysolution-list-constants';
-import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants';
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
+import { validate } from '@kbn/securitysolution-io-ts-utils';
import type {
InternalArtifactCompleteSchema,
TranslatedEntry,
@@ -74,18 +75,31 @@ export type ArtifactListId =
| typeof ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID
| typeof ENDPOINT_BLOCKLISTS_LIST_ID;
-export async function getFilteredEndpointExceptionList({
+export function convertExceptionsToEndpointFormat(
+ exceptions: ExceptionListItemSchema[],
+ schemaVersion: string
+) {
+ const translatedExceptions = {
+ entries: translateToEndpointExceptions(exceptions, schemaVersion),
+ };
+ const [validated, errors] = validate(translatedExceptions, wrappedTranslatedExceptionList);
+ if (errors != null) {
+ throw new Error(errors);
+ }
+
+ return validated as WrappedTranslatedExceptionList;
+}
+
+export async function getFilteredEndpointExceptionListRaw({
elClient,
filter,
listId,
- schemaVersion,
}: {
elClient: ExceptionListClient;
filter: string;
listId: ArtifactListId;
- schemaVersion: string;
-}): Promise {
- const exceptions: WrappedTranslatedExceptionList = { entries: [] };
+}): Promise {
+ let exceptions: ExceptionListItemSchema[] = [];
let page = 1;
let paging = true;
@@ -94,63 +108,39 @@ export async function getFilteredEndpointExceptionList({
listId,
namespaceType: 'agnostic',
filter,
- perPage: 100,
+ perPage: 1000,
page,
sortField: 'created_at',
sortOrder: 'desc',
});
if (response?.data !== undefined) {
- exceptions.entries = exceptions.entries.concat(
- translateToEndpointExceptions(response.data, schemaVersion)
- );
+ exceptions = exceptions.concat(response.data);
- paging = (page - 1) * 100 + response.data.length < response.total;
+ paging = (page - 1) * 1000 + response.data.length < response.total;
page++;
} else {
break;
}
}
- const [validated, errors] = validate(exceptions, wrappedTranslatedExceptionList);
- if (errors != null) {
- throw new Error(errors);
- }
- return validated as WrappedTranslatedExceptionList;
+ return exceptions;
}
-export async function getEndpointExceptionList({
+export async function getAllItemsFromEndpointExceptionList({
elClient,
listId,
os,
- policyId,
- schemaVersion,
}: {
elClient: ExceptionListClient;
- listId?: ArtifactListId;
+ listId: ArtifactListId;
os: string;
- policyId?: string;
- schemaVersion: string;
-}): Promise {
+}): Promise {
const osFilter = `exception-list-agnostic.attributes.os_types:\"${os}\"`;
- const policyFilter = `(exception-list-agnostic.attributes.tags:\"policy:all\"${
- policyId ? ` or exception-list-agnostic.attributes.tags:\"policy:${policyId}\"` : ''
- })`;
- // for endpoint list
- if (!listId || listId === ENDPOINT_LIST_ID) {
- return getFilteredEndpointExceptionList({
- elClient,
- schemaVersion,
- filter: `${osFilter}`,
- listId: ENDPOINT_LIST_ID,
- });
- }
- // for TAs, EFs, Host IEs and Blocklists
- return getFilteredEndpointExceptionList({
+ return getFilteredEndpointExceptionListRaw({
elClient,
- schemaVersion,
- filter: `${osFilter} and ${policyFilter}`,
+ filter: osFilter,
listId,
});
}
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_details_by_id.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_details_by_id.ts
index 1b68c376519bd..9c85a193d712d 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_details_by_id.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_details_by_id.ts
@@ -118,7 +118,7 @@ export const getActionDetailsById = async (
});
const { isCompleted, completedAt, wasSuccessful, errors, outputs, agentState } =
- getActionCompletionInfo(normalizedActionRequest.agents, actionResponses);
+ getActionCompletionInfo(normalizedActionRequest, actionResponses);
const { isExpired, status } = getActionStatus({
expirationDate: normalizedActionRequest.expiration,
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.ts
index 39f6ef52783cd..f932eb7df23f9 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.ts
@@ -276,7 +276,7 @@ const getActionDetailsList = async ({
// find the specific response's details using that set of matching responses
const { isCompleted, completedAt, wasSuccessful, errors, agentState, outputs } =
- getActionCompletionInfo(action.agents, matchedResponses);
+ getActionCompletionInfo(action, matchedResponses);
const { isExpired, status } = getActionStatus({
expirationDate: action.expiration,
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts
index 8be12acae17c1..b5c2119281e75 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts
@@ -123,17 +123,42 @@ describe('When using Actions service utilities', () => {
});
it('should show complete `false` if no action ids', () => {
- expect(getActionCompletionInfo([], [])).toEqual({ ...NOT_COMPLETED_OUTPUT, agentState: {} });
+ expect(
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: [],
+ })
+ ),
+ []
+ )
+ ).toEqual({
+ ...NOT_COMPLETED_OUTPUT,
+ agentState: {},
+ });
});
it('should show complete as `false` if no responses', () => {
- expect(getActionCompletionInfo(['123'], [])).toEqual(NOT_COMPLETED_OUTPUT);
+ expect(
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: ['123'],
+ })
+ ),
+ []
+ )
+ ).toEqual(NOT_COMPLETED_OUTPUT);
});
it('should show complete as `false` if no Endpoint response', () => {
expect(
getActionCompletionInfo(
- ['123'],
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: ['123'],
+ })
+ ),
[
fleetActionGenerator.generateActivityLogActionResponse({
item: { data: { action_id: '123' } },
@@ -156,7 +181,16 @@ describe('When using Actions service utilities', () => {
},
},
});
- expect(getActionCompletionInfo(['123'], [endpointResponse])).toEqual({
+ expect(
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: ['123'],
+ })
+ ),
+ [endpointResponse]
+ )
+ ).toEqual({
isCompleted: true,
completedAt: COMPLETED_AT,
errors: undefined,
@@ -201,7 +235,16 @@ describe('When using Actions service utilities', () => {
},
},
});
- expect(getActionCompletionInfo(['123'], [endpointResponse])).toEqual({
+ expect(
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: ['123'],
+ })
+ ),
+ [endpointResponse]
+ )
+ ).toEqual({
isCompleted: true,
completedAt: COMPLETED_AT,
errors: undefined,
@@ -255,7 +298,16 @@ describe('When using Actions service utilities', () => {
});
it('should show `wasSuccessful` as `false` if endpoint action response has error', () => {
- expect(getActionCompletionInfo(['123'], [endpointResponseAtError])).toEqual({
+ expect(
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: ['123'],
+ })
+ ),
+ [endpointResponseAtError]
+ )
+ ).toEqual({
completedAt: endpointResponseAtError.item.data['@timestamp'],
errors: ['Endpoint action response error: endpoint failed to apply'],
isCompleted: true,
@@ -273,7 +325,16 @@ describe('When using Actions service utilities', () => {
});
it('should show `wasSuccessful` as `false` if fleet action response has error (no endpoint response)', () => {
- expect(getActionCompletionInfo(['123'], [fleetResponseAtError])).toEqual({
+ expect(
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: ['123'],
+ })
+ ),
+ [fleetResponseAtError]
+ )
+ ).toEqual({
completedAt: fleetResponseAtError.item.data.completed_at,
errors: ['Fleet action response error: agent failed to deliver'],
isCompleted: true,
@@ -292,7 +353,14 @@ describe('When using Actions service utilities', () => {
it('should include both fleet and endpoint errors if both responses returned failure', () => {
expect(
- getActionCompletionInfo(['123'], [fleetResponseAtError, endpointResponseAtError])
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: ['123'],
+ })
+ ),
+ [fleetResponseAtError, endpointResponseAtError]
+ )
).toEqual({
completedAt: endpointResponseAtError.item.data['@timestamp'],
errors: [
@@ -374,7 +442,16 @@ describe('When using Actions service utilities', () => {
});
it('should show complete as `false` if no responses', () => {
- expect(getActionCompletionInfo(agentIds, [])).toEqual({
+ expect(
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: agentIds,
+ })
+ ),
+ []
+ )
+ ).toEqual({
...NOT_COMPLETED_OUTPUT,
agentState: {
...NOT_COMPLETED_OUTPUT.agentState,
@@ -396,14 +473,21 @@ describe('When using Actions service utilities', () => {
it('should complete as `false` if at least one agent id has not received a response', () => {
expect(
- getActionCompletionInfo(agentIds, [
- ...action123Responses,
-
- // Action id: 456 === Not complete (only fleet response)
- action456Responses[0],
-
- ...action789Responses,
- ])
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: agentIds,
+ })
+ ),
+ [
+ ...action123Responses,
+
+ // Action id: 456 === Not complete (only fleet response)
+ action456Responses[0],
+
+ ...action789Responses,
+ ]
+ )
).toEqual({
...NOT_COMPLETED_OUTPUT,
outputs: expect.any(Object),
@@ -432,11 +516,14 @@ describe('When using Actions service utilities', () => {
it('should show complete as `true` if all agent response were received', () => {
expect(
- getActionCompletionInfo(agentIds, [
- ...action123Responses,
- ...action456Responses,
- ...action789Responses,
- ])
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: agentIds,
+ })
+ ),
+ [...action123Responses, ...action456Responses, ...action789Responses]
+ )
).toEqual({
isCompleted: true,
completedAt: COMPLETED_AT,
@@ -471,14 +558,21 @@ describe('When using Actions service utilities', () => {
action456Responses[0].item.data['@timestamp'] = '2022-05-06T12:50:19.747Z';
expect(
- getActionCompletionInfo(agentIds, [
- ...action123Responses,
-
- // Action id: 456 === is complete with only a fleet response that has `error`
- action456Responses[0],
-
- ...action789Responses,
- ])
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: agentIds,
+ })
+ ),
+ [
+ ...action123Responses,
+
+ // Action id: 456 === is complete with only a fleet response that has `error`
+ action456Responses[0],
+
+ ...action789Responses,
+ ]
+ )
).toEqual({
completedAt: '2022-05-06T12:50:19.747Z',
errors: ['Fleet action response error: something is no good'],
@@ -528,14 +622,21 @@ describe('When using Actions service utilities', () => {
};
expect(
- getActionCompletionInfo(agentIds, [
- ...action123Responses,
-
- // Action id: 456 === Not complete (only fleet response)
- action456Responses[0],
-
- ...action789Responses,
- ])
+ getActionCompletionInfo(
+ mapToNormalizedActionRequest(
+ fleetActionGenerator.generate({
+ agents: agentIds,
+ })
+ ),
+ [
+ ...action123Responses,
+
+ // Action id: 456 === Not complete (only fleet response)
+ action456Responses[0],
+
+ ...action789Responses,
+ ]
+ )
).toEqual({
...NOT_COMPLETED_OUTPUT,
agentState: {
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.ts
index 188e2b6153ede..1d16c944d2cef 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.ts
@@ -115,11 +115,12 @@ type ActionCompletionInfo = Pick<
>;
export const getActionCompletionInfo = (
- /** List of agents that the action was sent to */
- agentIds: string[],
+ /** The normalized action request */
+ action: NormalizedActionRequest,
/** List of action Log responses received for the action */
actionResponses: Array
): ActionCompletionInfo => {
+ const agentIds = action.agents;
const completedInfo: ActionCompletionInfo = {
completedAt: undefined,
errors: undefined,
@@ -191,6 +192,25 @@ export const getActionCompletionInfo = (
}
}
+ // If the action request has an Error, then we'll never get actual response from all of the agents
+ // to which this action sent. In this case, we adjust the completion information to all be "complete"
+ // and un-successful
+ if (action.error?.message) {
+ const errorMessage = action.error.message;
+
+ completedInfo.completedAt = action.createdAt;
+ completedInfo.isCompleted = true;
+ completedInfo.wasSuccessful = false;
+ completedInfo.errors = [errorMessage];
+
+ Object.values(completedInfo.agentState).forEach((agentState) => {
+ agentState.completedAt = action.createdAt;
+ agentState.isCompleted = true;
+ agentState.wasSuccessful = false;
+ agentState.errors = [errorMessage];
+ });
+ }
+
return completedInfo;
};
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.test.ts
index 5df2d67eeb582..ce9e8e2fc46c8 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.test.ts
@@ -70,5 +70,17 @@ describe('artifact_client', () => {
});
expect(fleetArtifactClient.deleteArtifact).toHaveBeenCalledWith('123');
});
+
+ test('can bulk delete artifacts', async () => {
+ await artifactClient.bulkDeleteArtifacts([
+ 'endpoint-trustlist-linux-v1-sha26hash',
+ 'endpoint-trustlist-windows-v1-sha26hash',
+ ]);
+ expect(fleetArtifactClient.listArtifacts).toHaveBeenCalledTimes(0);
+ expect(fleetArtifactClient.bulkDeleteArtifacts).toHaveBeenCalledWith([
+ 'endpoint-trustlist-linux-v1-sha26hash',
+ 'endpoint-trustlist-windows-v1-sha26hash',
+ ]);
+ });
});
});
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts
index ed5723ddccf0e..3e00310a5bb64 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/artifact_client.ts
@@ -24,6 +24,8 @@ export interface EndpointArtifactClientInterface {
deleteArtifact(id: string): Promise;
+ bulkDeleteArtifacts(ids: string[]): Promise;
+
listArtifacts(options?: ListArtifactsProps): Promise>;
}
@@ -96,4 +98,8 @@ export class EndpointArtifactClient implements EndpointArtifactClientInterface {
const artifactId = (await this.getArtifact(id))?.id!;
return this.fleetArtifacts.deleteArtifact(artifactId);
}
+
+ async bulkDeleteArtifacts(ids: string[]): Promise {
+ return this.fleetArtifacts.bulkDeleteArtifacts(ids);
+ }
}
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts
index aaf16057a0df9..cb947b5221dbb 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts
@@ -5,7 +5,11 @@
* 2.0.
*/
-import { savedObjectsClientMock, loggingSystemMock } from '@kbn/core/server/mocks';
+import {
+ savedObjectsClientMock,
+ loggingSystemMock,
+ elasticsearchServiceMock,
+} from '@kbn/core/server/mocks';
import type { Logger } from '@kbn/core/server';
import type { PackagePolicyClient } from '@kbn/fleet-plugin/server';
import { createPackagePolicyServiceMock } from '@kbn/fleet-plugin/server/mocks';
@@ -88,6 +92,8 @@ export const buildManifestManagerContextMock = (
artifactClient: createEndpointArtifactClientMock(),
logger: loggingSystemMock.create().get() as jest.Mocked,
experimentalFeatures: parseExperimentalConfigValue([]).features,
+ packagerTaskPackagePolicyUpdateBatchSize: 10,
+ esClient: elasticsearchServiceMock.createElasticsearchClient(),
};
};
@@ -127,7 +133,7 @@ export const getManifestManagerMock = (
context.exceptionListClient.findExceptionListItem = jest
.fn()
.mockRejectedValue(new Error('unexpected thing happened'));
- return super.buildExceptionListArtifacts();
+ return super.buildExceptionListArtifacts([]);
case ManifestManagerMockType.NormalFlow:
return getMockArtifactsWithDiff();
}
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts
index 28933fb35e81c..f6859015f42fc 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts
@@ -349,10 +349,22 @@ describe('ManifestManager', () => {
test('Builds fully new manifest if no baseline parameter passed and present exception list items', async () => {
const exceptionListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] });
- const trustedAppListItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
- const eventFiltersListItem = getExceptionListItemSchemaMock({ os_types: ['windows'] });
- const hostIsolationExceptionsItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
- const blocklistsListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] });
+ const trustedAppListItem = getExceptionListItemSchemaMock({
+ os_types: ['linux'],
+ tags: ['policy:all'],
+ });
+ const eventFiltersListItem = getExceptionListItemSchemaMock({
+ os_types: ['windows'],
+ tags: ['policy:all'],
+ });
+ const hostIsolationExceptionsItem = getExceptionListItemSchemaMock({
+ os_types: ['linux'],
+ tags: ['policy:all'],
+ });
+ const blocklistsListItem = getExceptionListItemSchemaMock({
+ os_types: ['macos'],
+ tags: ['policy:all'],
+ });
const context = buildManifestManagerContextMock({});
const manifestManager = new ManifestManager(context);
@@ -417,10 +429,22 @@ describe('ManifestManager', () => {
test('Reuses artifacts when baseline parameter passed and present exception list items', async () => {
const exceptionListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] });
- const trustedAppListItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
- const eventFiltersListItem = getExceptionListItemSchemaMock({ os_types: ['windows'] });
- const hostIsolationExceptionsItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
- const blocklistsListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] });
+ const trustedAppListItem = getExceptionListItemSchemaMock({
+ os_types: ['linux'],
+ tags: ['policy:all'],
+ });
+ const eventFiltersListItem = getExceptionListItemSchemaMock({
+ os_types: ['windows'],
+ tags: ['policy:all'],
+ });
+ const hostIsolationExceptionsItem = getExceptionListItemSchemaMock({
+ os_types: ['linux'],
+ tags: ['policy:all'],
+ });
+ const blocklistsListItem = getExceptionListItemSchemaMock({
+ os_types: ['macos'],
+ tags: ['policy:all'],
+ });
const context = buildManifestManagerContextMock({});
const manifestManager = new ManifestManager(context);
@@ -486,15 +510,18 @@ describe('ManifestManager', () => {
}
});
- //
test('Builds manifest with policy specific exception list items for trusted apps', async () => {
const exceptionListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] });
- const trustedAppListItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
+ const trustedAppListItem = getExceptionListItemSchemaMock({
+ os_types: ['linux'],
+ tags: ['policy:all'],
+ });
const trustedAppListItemPolicy2 = getExceptionListItemSchemaMock({
os_types: ['linux'],
entries: [
{ field: 'other.field', operator: 'included', type: 'match', value: 'other value' },
],
+ tags: [`policy:${TEST_POLICY_ID_2}`],
});
const context = buildManifestManagerContextMock({});
const manifestManager = new ManifestManager(context);
@@ -502,8 +529,7 @@ describe('ManifestManager', () => {
context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
[ENDPOINT_TRUSTED_APPS_LIST_ID]: {
- linux: [trustedAppListItem],
- [`linux-${TEST_POLICY_ID_2}`]: [trustedAppListItem, trustedAppListItemPolicy2],
+ linux: [trustedAppListItem, trustedAppListItemPolicy2],
},
});
context.packagePolicyService.listIds = mockPolicyListIdsResponse([
@@ -574,14 +600,10 @@ describe('ManifestManager', () => {
])
).resolves.toStrictEqual([]);
- expect(context.artifactClient.deleteArtifact).toHaveBeenNthCalledWith(
- 1,
- ARTIFACT_ID_EXCEPTIONS_MACOS
- );
- expect(context.artifactClient.deleteArtifact).toHaveBeenNthCalledWith(
- 2,
- ARTIFACT_ID_EXCEPTIONS_WINDOWS
- );
+ expect(context.artifactClient.bulkDeleteArtifacts).toHaveBeenCalledWith([
+ ARTIFACT_ID_EXCEPTIONS_MACOS,
+ ARTIFACT_ID_EXCEPTIONS_WINDOWS,
+ ]);
});
test('Returns errors for partial failures', async () => {
@@ -590,10 +612,11 @@ describe('ManifestManager', () => {
const manifestManager = new ManifestManager(context);
const error = new Error();
- artifactClient.deleteArtifact.mockImplementation(async (id) => {
- if (id === ARTIFACT_ID_EXCEPTIONS_WINDOWS) {
- throw error;
+ artifactClient.bulkDeleteArtifacts.mockImplementation(async (ids): Promise => {
+ if (ids[1] === ARTIFACT_ID_EXCEPTIONS_WINDOWS) {
+ return [error];
}
+ return [];
});
await expect(
@@ -603,15 +626,11 @@ describe('ManifestManager', () => {
])
).resolves.toStrictEqual([error]);
- expect(artifactClient.deleteArtifact).toHaveBeenCalledTimes(2);
- expect(artifactClient.deleteArtifact).toHaveBeenNthCalledWith(
- 1,
- ARTIFACT_ID_EXCEPTIONS_MACOS
- );
- expect(artifactClient.deleteArtifact).toHaveBeenNthCalledWith(
- 2,
- ARTIFACT_ID_EXCEPTIONS_WINDOWS
- );
+ expect(artifactClient.bulkDeleteArtifacts).toHaveBeenCalledTimes(1);
+ expect(context.artifactClient.bulkDeleteArtifacts).toHaveBeenCalledWith([
+ ARTIFACT_ID_EXCEPTIONS_MACOS,
+ ARTIFACT_ID_EXCEPTIONS_WINDOWS,
+ ]);
});
});
@@ -790,12 +809,6 @@ describe('ManifestManager', () => {
total: items.length,
});
- const toNewPackagePolicy = (packagePolicy: PackagePolicy) => {
- const { id, revision, updated_at: updatedAt, updated_by: updatedBy, ...rest } = packagePolicy;
-
- return rest;
- };
-
test('Should not dispatch if no policies', async () => {
const context = buildManifestManagerContextMock({});
const manifestManager = new ManifestManager(context);
@@ -807,7 +820,7 @@ describe('ManifestManager', () => {
await expect(manifestManager.tryDispatch(manifest)).resolves.toStrictEqual([]);
- expect(context.packagePolicyService.update).toHaveBeenCalledTimes(0);
+ expect(context.packagePolicyService.bulkUpdate).toHaveBeenCalledTimes(0);
});
test('Should return errors if invalid config for package policy', async () => {
@@ -825,7 +838,7 @@ describe('ManifestManager', () => {
new EndpointError(`Package Policy ${TEST_POLICY_ID_1} has no 'inputs[0].config'`),
]);
- expect(context.packagePolicyService.update).toHaveBeenCalledTimes(0);
+ expect(context.packagePolicyService.bulkUpdate).toHaveBeenCalledTimes(0);
});
test('Should not dispatch if semantic version has not changed', async () => {
@@ -854,7 +867,7 @@ describe('ManifestManager', () => {
await expect(manifestManager.tryDispatch(manifest)).resolves.toStrictEqual([]);
- expect(context.packagePolicyService.update).toHaveBeenCalledTimes(0);
+ expect(context.packagePolicyService.bulkUpdate).toHaveBeenCalledTimes(0);
});
test('Should dispatch to only policies where list of artifacts changed', async () => {
@@ -898,17 +911,16 @@ describe('ManifestManager', () => {
},
}),
]);
- context.packagePolicyService.update = jest.fn().mockResolvedValue({});
+ context.packagePolicyService.bulkUpdate = jest.fn().mockResolvedValue({});
await expect(manifestManager.tryDispatch(manifest)).resolves.toStrictEqual([]);
- expect(context.packagePolicyService.update).toHaveBeenCalledTimes(1);
- expect(context.packagePolicyService.update).toHaveBeenNthCalledWith(
+ expect(context.packagePolicyService.bulkUpdate).toHaveBeenCalledTimes(1);
+ expect(context.packagePolicyService.bulkUpdate).toHaveBeenNthCalledWith(
1,
expect.anything(),
- undefined,
- TEST_POLICY_ID_1,
- toNewPackagePolicy(
+ context.esClient,
+ [
createPackagePolicyWithConfigMock({
id: TEST_POLICY_ID_1,
config: {
@@ -922,8 +934,8 @@ describe('ManifestManager', () => {
},
},
},
- })
- )
+ }),
+ ]
);
});
@@ -970,17 +982,16 @@ describe('ManifestManager', () => {
},
}),
]);
- context.packagePolicyService.update = jest.fn().mockResolvedValue({});
+ context.packagePolicyService.bulkUpdate = jest.fn().mockResolvedValue({});
await expect(manifestManager.tryDispatch(manifest)).resolves.toStrictEqual([]);
- expect(context.packagePolicyService.update).toHaveBeenCalledTimes(1);
- expect(context.packagePolicyService.update).toHaveBeenNthCalledWith(
+ expect(context.packagePolicyService.bulkUpdate).toHaveBeenCalledTimes(1);
+ expect(context.packagePolicyService.bulkUpdate).toHaveBeenNthCalledWith(
1,
expect.anything(),
- undefined,
- TEST_POLICY_ID_1,
- toNewPackagePolicy(
+ context.esClient,
+ [
createPackagePolicyWithConfigMock({
id: TEST_POLICY_ID_1,
config: {
@@ -994,8 +1005,8 @@ describe('ManifestManager', () => {
},
},
},
- })
- )
+ }),
+ ]
);
});
@@ -1033,17 +1044,13 @@ describe('ManifestManager', () => {
},
}),
]);
- context.packagePolicyService.update = jest.fn().mockImplementation(async (...args) => {
- if (args[2] === TEST_POLICY_ID_2) {
- throw error;
- } else {
- return {};
- }
- });
+ context.packagePolicyService.bulkUpdate = jest
+ .fn()
+ .mockResolvedValue({ updatedPolicies: [{}], failedPolicies: [{ error }] });
await expect(manifestManager.tryDispatch(manifest)).resolves.toStrictEqual([error]);
- expect(context.packagePolicyService.update).toHaveBeenCalledTimes(2);
+ expect(context.packagePolicyService.bulkUpdate).toHaveBeenCalledTimes(1);
});
});
@@ -1074,9 +1081,9 @@ describe('ManifestManager', () => {
const artifactToBeRemoved = await context.artifactClient.getArtifact('');
expect(artifactToBeRemoved).not.toBeUndefined();
- expect(context.artifactClient.deleteArtifact).toHaveBeenCalledWith(
- getArtifactId(artifactToBeRemoved!)
- );
+ expect(context.artifactClient.bulkDeleteArtifacts).toHaveBeenCalledWith([
+ getArtifactId(artifactToBeRemoved!),
+ ]);
});
test('When there is no artifact to be removed', async () => {
@@ -1113,7 +1120,7 @@ describe('ManifestManager', () => {
await manifestManager.cleanup(manifest);
- expect(context.artifactClient.deleteArtifact).toHaveBeenCalledTimes(0);
+ expect(context.artifactClient.bulkDeleteArtifacts).toHaveBeenCalledTimes(0);
});
});
});
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts
index 440e6366e262f..b7c98ef1b8773 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts
@@ -7,17 +7,20 @@
import pMap from 'p-map';
import semver from 'semver';
-import { isEqual, isEmpty } from 'lodash';
+import { isEqual, isEmpty, chunk } from 'lodash';
+import type { ElasticsearchClient } from '@kbn/core/server';
import { type Logger, type SavedObjectsClientContract } from '@kbn/core/server';
import {
ENDPOINT_EVENT_FILTERS_LIST_ID,
ENDPOINT_TRUSTED_APPS_LIST_ID,
ENDPOINT_BLOCKLISTS_LIST_ID,
ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
+ ENDPOINT_LIST_ID,
} from '@kbn/securitysolution-list-constants';
-import type { ListResult } from '@kbn/fleet-plugin/common';
+import type { ListResult, PackagePolicy } from '@kbn/fleet-plugin/common';
import type { PackagePolicyClient } from '@kbn/fleet-plugin/server';
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
+import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import type { ManifestSchemaVersion } from '../../../../../common/endpoint/schema/common';
import type { ManifestSchema } from '../../../../../common/endpoint/schema/manifest';
import { manifestDispatchSchema } from '../../../../../common/endpoint/schema/manifest';
@@ -26,9 +29,10 @@ import type { ArtifactListId } from '../../../lib/artifacts';
import {
ArtifactConstants,
buildArtifact,
+ getAllItemsFromEndpointExceptionList,
getArtifactId,
- getEndpointExceptionList,
Manifest,
+ convertExceptionsToEndpointFormat,
} from '../../../lib/artifacts';
import type { InternalArtifactCompleteSchema } from '../../../schemas/artifacts';
import { internalArtifactCompleteSchema } from '../../../schemas/artifacts';
@@ -49,36 +53,35 @@ interface BuildArtifactsForOsOptions {
name: string;
}
-const iterateArtifactsBuildResult = async (
+const iterateArtifactsBuildResult = (
result: ArtifactsBuildResult,
- callback: (artifact: InternalArtifactCompleteSchema, policyId?: string) => Promise
+ callback: (artifact: InternalArtifactCompleteSchema, policyId?: string) => void
) => {
for (const artifact of result.defaultArtifacts) {
- await callback(artifact);
+ callback(artifact);
}
for (const policyId of Object.keys(result.policySpecificArtifacts)) {
for (const artifact of result.policySpecificArtifacts[policyId]) {
- await callback(artifact, policyId);
+ callback(artifact, policyId);
}
}
};
const iterateAllListItems = async (
- pageSupplier: (page: number) => Promise>,
- itemCallback: (item: T) => void
+ pageSupplier: (page: number, perPage: number) => Promise>,
+ itemCallback: (items: T[]) => void
) => {
let paging = true;
let page = 1;
+ const perPage = 1000;
while (paging) {
- const { items, total } = await pageSupplier(page);
+ const { items, total } = await pageSupplier(page, perPage);
- for (const item of items) {
- await itemCallback(item);
- }
+ itemCallback(items);
- paging = (page - 1) * 20 + items.length < total;
+ paging = (page - 1) * perPage + items.length < total;
page++;
}
};
@@ -90,6 +93,8 @@ export interface ManifestManagerContext {
packagePolicyService: PackagePolicyClient;
logger: Logger;
experimentalFeatures: ExperimentalFeatures;
+ packagerTaskPackagePolicyUpdateBatchSize: number;
+ esClient: ElasticsearchClient;
}
const getArtifactIds = (manifest: ManifestSchema) =>
@@ -108,6 +113,9 @@ export class ManifestManager {
protected logger: Logger;
protected schemaVersion: ManifestSchemaVersion;
protected experimentalFeatures: ExperimentalFeatures;
+ protected cachedExceptionsListsByOs: Map;
+ protected packagerTaskPackagePolicyUpdateBatchSize: number;
+ protected esClient: ElasticsearchClient;
constructor(context: ManifestManagerContext) {
this.artifactClient = context.artifactClient;
@@ -117,6 +125,10 @@ export class ManifestManager {
this.logger = context.logger;
this.schemaVersion = 'v1';
this.experimentalFeatures = context.experimentalFeatures;
+ this.cachedExceptionsListsByOs = new Map();
+ this.packagerTaskPackagePolicyUpdateBatchSize =
+ context.packagerTaskPackagePolicyUpdateBatchSize;
+ this.esClient = context.esClient;
}
/**
@@ -129,45 +141,44 @@ export class ManifestManager {
}
/**
- * Builds an artifact (one per supported OS) based on the current
- * state of exception-list-agnostic SOs.
+ * Search or get exceptions from the cached map by listId and OS and filter those by policyId/global
*/
- protected async buildExceptionListArtifact(os: string): Promise {
- return buildArtifact(
- await getEndpointExceptionList({
- elClient: this.exceptionListClient,
- schemaVersion: this.schemaVersion,
+ protected async getCachedExceptions({
+ elClient,
+ listId,
+ os,
+ policyId,
+ schemaVersion,
+ }: {
+ elClient: ExceptionListClient;
+ listId: ArtifactListId;
+ os: string;
+ policyId?: string;
+ schemaVersion: string;
+ }) {
+ if (!this.cachedExceptionsListsByOs.has(`${listId}-${os}`)) {
+ const itemsByListId = await getAllItemsFromEndpointExceptionList({
+ elClient,
os,
- }),
- this.schemaVersion,
- os,
- ArtifactConstants.GLOBAL_ALLOWLIST_NAME
- );
- }
-
- /**
- * Builds an array of artifacts (one per supported OS) based on the current
- * state of exception-list-agnostic SOs.
- *
- * @returns {Promise} An array of uncompressed artifacts built from exception-list-agnostic SOs.
- * @throws Throws/rejects if there are errors building the list.
- */
- protected async buildExceptionListArtifacts(): Promise {
- const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
- const policySpecificArtifacts: Record = {};
+ listId,
+ });
+ this.cachedExceptionsListsByOs.set(`${listId}-${os}`, itemsByListId);
+ }
- for (const os of ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS) {
- defaultArtifacts.push(await this.buildExceptionListArtifact(os));
+ const allExceptionsByListId = this.cachedExceptionsListsByOs.get(`${listId}-${os}`);
+ if (!allExceptionsByListId) {
+ throw new InvalidInternalManifestError(`Error getting exceptions for ${listId}-${os}`);
}
- await iterateAllListItems(
- (page) => this.listEndpointPolicyIds(page),
- async (policyId) => {
- policySpecificArtifacts[policyId] = defaultArtifacts;
- }
- );
+ const filter = (exception: ExceptionListItemSchema) =>
+ policyId
+ ? exception.tags.includes('policy:all') || exception.tags.includes(`policy:${policyId}`)
+ : exception.tags.includes('policy:all');
- return { defaultArtifacts, policySpecificArtifacts };
+ const exceptions: ExceptionListItemSchema[] =
+ listId === ENDPOINT_LIST_ID ? allExceptionsByListId : allExceptionsByListId.filter(filter);
+
+ return convertExceptionsToEndpointFormat(exceptions, schemaVersion);
}
/**
@@ -185,7 +196,7 @@ export class ManifestManager {
policyId?: string;
} & BuildArtifactsForOsOptions): Promise {
return buildArtifact(
- await getEndpointExceptionList({
+ await this.getCachedExceptions({
elClient: this.exceptionListClient,
schemaVersion: this.schemaVersion,
os,
@@ -198,13 +209,72 @@ export class ManifestManager {
);
}
+ /**
+ * Builds an artifact (by policy) based on the current state of the
+ * artifacts list (Trusted Apps, Host Iso. Exceptions, Event Filters, Blocklists)
+ * (which uses the `exception-list-agnostic` SO type)
+ */
+ protected async buildArtifactsByPolicy(
+ allPolicyIds: string[],
+ supportedOSs: string[],
+ osOptions: BuildArtifactsForOsOptions
+ ) {
+ const policySpecificArtifacts: Record = {};
+ await pMap(
+ allPolicyIds,
+ async (policyId) => {
+ for (const os of supportedOSs) {
+ policySpecificArtifacts[policyId] = policySpecificArtifacts[policyId] || [];
+ policySpecificArtifacts[policyId].push(
+ await this.buildArtifactsForOs({ os, policyId, ...osOptions })
+ );
+ }
+ },
+ {
+ concurrency: 5,
+ /** When set to false, instead of stopping when a promise rejects, it will wait for all the promises to
+ * settle and then reject with an aggregated error containing all the errors from the rejected promises. */
+ stopOnError: false,
+ }
+ );
+
+ return policySpecificArtifacts;
+ }
+
+ /**
+ * Builds an array of artifacts (one per supported OS) based on the current
+ * state of exception-list-agnostic SOs.
+ *
+ * @returns {Promise} An array of uncompressed artifacts built from exception-list-agnostic SOs.
+ * @throws Throws/rejects if there are errors building the list.
+ */
+ protected async buildExceptionListArtifacts(
+ allPolicyIds: string[]
+ ): Promise {
+ const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
+ const policySpecificArtifacts: Record = {};
+ const buildArtifactsForOsOptions: BuildArtifactsForOsOptions = {
+ listId: ENDPOINT_LIST_ID,
+ name: ArtifactConstants.GLOBAL_ALLOWLIST_NAME,
+ };
+
+ for (const os of ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS) {
+ defaultArtifacts.push(await this.buildArtifactsForOs({ os, ...buildArtifactsForOsOptions }));
+ }
+
+ allPolicyIds.forEach((policyId) => {
+ policySpecificArtifacts[policyId] = defaultArtifacts;
+ });
+
+ return { defaultArtifacts, policySpecificArtifacts };
+ }
+
/**
* Builds an array of artifacts (one per supported OS) based on the current state of the
* Trusted Apps list (which uses the `exception-list-agnostic` SO type)
*/
- protected async buildTrustedAppsArtifacts(): Promise {
+ protected async buildTrustedAppsArtifacts(allPolicyIds: string[]): Promise {
const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
- const policySpecificArtifacts: Record = {};
const buildArtifactsForOsOptions: BuildArtifactsForOsOptions = {
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
name: ArtifactConstants.GLOBAL_TRUSTED_APPS_NAME,
@@ -214,17 +284,12 @@ export class ManifestManager {
defaultArtifacts.push(await this.buildArtifactsForOs({ os, ...buildArtifactsForOsOptions }));
}
- await iterateAllListItems(
- (page) => this.listEndpointPolicyIds(page),
- async (policyId) => {
- for (const os of ArtifactConstants.SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS) {
- policySpecificArtifacts[policyId] = policySpecificArtifacts[policyId] || [];
- policySpecificArtifacts[policyId].push(
- await this.buildArtifactsForOs({ os, policyId, ...buildArtifactsForOsOptions })
- );
- }
- }
- );
+ const policySpecificArtifacts: Record =
+ await this.buildArtifactsByPolicy(
+ allPolicyIds,
+ ArtifactConstants.SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS,
+ buildArtifactsForOsOptions
+ );
return { defaultArtifacts, policySpecificArtifacts };
}
@@ -234,9 +299,10 @@ export class ManifestManager {
* Event Filters list
* @protected
*/
- protected async buildEventFiltersArtifacts(): Promise {
+ protected async buildEventFiltersArtifacts(
+ allPolicyIds: string[]
+ ): Promise {
const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
- const policySpecificArtifacts: Record = {};
const buildArtifactsForOsOptions: BuildArtifactsForOsOptions = {
listId: ENDPOINT_EVENT_FILTERS_LIST_ID,
name: ArtifactConstants.GLOBAL_EVENT_FILTERS_NAME,
@@ -246,17 +312,12 @@ export class ManifestManager {
defaultArtifacts.push(await this.buildArtifactsForOs({ os, ...buildArtifactsForOsOptions }));
}
- await iterateAllListItems(
- (page) => this.listEndpointPolicyIds(page),
- async (policyId) => {
- for (const os of ArtifactConstants.SUPPORTED_EVENT_FILTERS_OPERATING_SYSTEMS) {
- policySpecificArtifacts[policyId] = policySpecificArtifacts[policyId] || [];
- policySpecificArtifacts[policyId].push(
- await this.buildArtifactsForOs({ os, policyId, ...buildArtifactsForOsOptions })
- );
- }
- }
- );
+ const policySpecificArtifacts: Record =
+ await this.buildArtifactsByPolicy(
+ allPolicyIds,
+ ArtifactConstants.SUPPORTED_EVENT_FILTERS_OPERATING_SYSTEMS,
+ buildArtifactsForOsOptions
+ );
return { defaultArtifacts, policySpecificArtifacts };
}
@@ -266,29 +327,23 @@ export class ManifestManager {
* Blocklist list
* @protected
*/
- protected async buildBlocklistArtifacts(): Promise {
+ protected async buildBlocklistArtifacts(allPolicyIds: string[]): Promise {
const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
- const policySpecificArtifacts: Record = {};
const buildArtifactsForOsOptions: BuildArtifactsForOsOptions = {
listId: ENDPOINT_BLOCKLISTS_LIST_ID,
name: ArtifactConstants.GLOBAL_BLOCKLISTS_NAME,
};
- for (const os of ArtifactConstants.SUPPORTED_EVENT_FILTERS_OPERATING_SYSTEMS) {
+ for (const os of ArtifactConstants.SUPPORTED_BLOCKLISTS_OPERATING_SYSTEMS) {
defaultArtifacts.push(await this.buildArtifactsForOs({ os, ...buildArtifactsForOsOptions }));
}
- await iterateAllListItems(
- (page) => this.listEndpointPolicyIds(page),
- async (policyId) => {
- for (const os of ArtifactConstants.SUPPORTED_EVENT_FILTERS_OPERATING_SYSTEMS) {
- policySpecificArtifacts[policyId] = policySpecificArtifacts[policyId] || [];
- policySpecificArtifacts[policyId].push(
- await this.buildArtifactsForOs({ os, policyId, ...buildArtifactsForOsOptions })
- );
- }
- }
- );
+ const policySpecificArtifacts: Record =
+ await this.buildArtifactsByPolicy(
+ allPolicyIds,
+ ArtifactConstants.SUPPORTED_BLOCKLISTS_OPERATING_SYSTEMS,
+ buildArtifactsForOsOptions
+ );
return { defaultArtifacts, policySpecificArtifacts };
}
@@ -299,9 +354,10 @@ export class ManifestManager {
* @returns
*/
- protected async buildHostIsolationExceptionsArtifacts(): Promise {
+ protected async buildHostIsolationExceptionsArtifacts(
+ allPolicyIds: string[]
+ ): Promise {
const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
- const policySpecificArtifacts: Record = {};
const buildArtifactsForOsOptions: BuildArtifactsForOsOptions = {
listId: ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
name: ArtifactConstants.GLOBAL_HOST_ISOLATION_EXCEPTIONS_NAME,
@@ -311,17 +367,12 @@ export class ManifestManager {
defaultArtifacts.push(await this.buildArtifactsForOs({ os, ...buildArtifactsForOsOptions }));
}
- await iterateAllListItems(
- (page) => this.listEndpointPolicyIds(page),
- async (policyId) => {
- for (const os of ArtifactConstants.SUPPORTED_HOST_ISOLATION_EXCEPTIONS_OPERATING_SYSTEMS) {
- policySpecificArtifacts[policyId] = policySpecificArtifacts[policyId] || [];
- policySpecificArtifacts[policyId].push(
- await this.buildArtifactsForOs({ os, policyId, ...buildArtifactsForOsOptions })
- );
- }
- }
- );
+ const policySpecificArtifacts: Record =
+ await this.buildArtifactsByPolicy(
+ allPolicyIds,
+ ArtifactConstants.SUPPORTED_HOST_ISOLATION_EXCEPTIONS_OPERATING_SYSTEMS,
+ buildArtifactsForOsOptions
+ );
return { defaultArtifacts, policySpecificArtifacts };
}
@@ -385,16 +436,21 @@ export class ManifestManager {
* @returns {Promise} Any errors encountered.
*/
public async deleteArtifacts(artifactIds: string[]): Promise {
- const errors: Error[] = [];
- for (const artifactId of artifactIds) {
- try {
- await this.artifactClient.deleteArtifact(artifactId);
+ try {
+ if (isEmpty(artifactIds)) {
+ return [];
+ }
+ const errors = await this.artifactClient.bulkDeleteArtifacts(artifactIds);
+ if (!isEmpty(errors)) {
+ return errors;
+ }
+ for (const artifactId of artifactIds) {
this.logger.info(`Cleaned up artifact ${artifactId}`);
- } catch (err) {
- errors.push(err);
}
+ return [];
+ } catch (err) {
+ return [err];
}
- return errors;
}
/**
@@ -461,14 +517,18 @@ export class ManifestManager {
public async buildNewManifest(
baselineManifest: Manifest = ManifestManager.createDefaultManifest(this.schemaVersion)
): Promise {
+ const allPolicyIds = await this.listEndpointPolicyIds();
const results = await Promise.all([
- this.buildExceptionListArtifacts(),
- this.buildTrustedAppsArtifacts(),
- this.buildEventFiltersArtifacts(),
- this.buildHostIsolationExceptionsArtifacts(),
- this.buildBlocklistArtifacts(),
+ this.buildExceptionListArtifacts(allPolicyIds),
+ this.buildTrustedAppsArtifacts(allPolicyIds),
+ this.buildEventFiltersArtifacts(allPolicyIds),
+ this.buildHostIsolationExceptionsArtifacts(allPolicyIds),
+ this.buildBlocklistArtifacts(allPolicyIds),
]);
+ // Clear cache as the ManifestManager instance is reused on every run.
+ this.cachedExceptionsListsByOs.clear();
+
const manifest = new Manifest({
schemaVersion: this.schemaVersion,
semanticVersion: baselineManifest.getSemanticVersion(),
@@ -476,7 +536,7 @@ export class ManifestManager {
});
for (const result of results) {
- await iterateArtifactsBuildResult(result, async (artifact, policyId) => {
+ iterateArtifactsBuildResult(result, (artifact, policyId) => {
const artifactToAdd = baselineManifest.getArtifact(getArtifactId(artifact)) || artifact;
if (!internalArtifactCompleteSchema.is(artifactToAdd)) {
throw new EndpointError(
@@ -500,62 +560,83 @@ export class ManifestManager {
* @returns {Promise} Any errors encountered.
*/
public async tryDispatch(manifest: Manifest): Promise {
- const errors: Error[] = [];
-
+ const allPackagePolicies: PackagePolicy[] = [];
await iterateAllListItems(
- (page) => this.listEndpointPolicies(page),
- async (packagePolicy) => {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- const { id, revision, updated_at, updated_by, ...newPackagePolicy } = packagePolicy;
- if (newPackagePolicy.inputs.length > 0 && newPackagePolicy.inputs[0].config !== undefined) {
- const oldManifest = newPackagePolicy.inputs[0].config.artifact_manifest ?? {
- value: {},
- };
-
- const newManifestVersion = manifest.getSemanticVersion();
- if (semver.gt(newManifestVersion, oldManifest.value.manifest_version)) {
- const serializedManifest = manifest.toPackagePolicyManifest(packagePolicy.id);
-
- if (!manifestDispatchSchema.is(serializedManifest)) {
- errors.push(
- new EndpointError(
- `Invalid manifest for policy ${packagePolicy.id}`,
- serializedManifest
- )
- );
- } else if (!manifestsEqual(serializedManifest, oldManifest.value)) {
- newPackagePolicy.inputs[0].config.artifact_manifest = { value: serializedManifest };
-
- try {
- await this.packagePolicyService.update(
- this.savedObjectsClient,
- // @ts-expect-error TS2345
- undefined,
- id,
- newPackagePolicy
- );
- this.logger.debug(
- `Updated package policy ${id} with manifest version ${manifest.getSemanticVersion()}`
- );
- } catch (err) {
- errors.push(err);
- }
- } else {
- this.logger.debug(
- `No change in manifest content for package policy: ${id}. Staying on old version`
- );
- }
+ (page, perPage) => this.listEndpointPolicies(page, perPage),
+ (packagePoliciesBatch) => {
+ allPackagePolicies.push(...packagePoliciesBatch);
+ }
+ );
+
+ const packagePoliciesToUpdate: PackagePolicy[] = [];
+
+ const errors: Error[] = [];
+ allPackagePolicies.forEach((packagePolicy) => {
+ const { id } = packagePolicy;
+ if (packagePolicy.inputs.length > 0 && packagePolicy.inputs[0].config !== undefined) {
+ const oldManifest = packagePolicy.inputs[0].config.artifact_manifest ?? {
+ value: {},
+ };
+
+ const newManifestVersion = manifest.getSemanticVersion();
+ if (semver.gt(newManifestVersion, oldManifest.value.manifest_version)) {
+ const serializedManifest = manifest.toPackagePolicyManifest(id);
+
+ if (!manifestDispatchSchema.is(serializedManifest)) {
+ errors.push(new EndpointError(`Invalid manifest for policy ${id}`, serializedManifest));
+ } else if (!manifestsEqual(serializedManifest, oldManifest.value)) {
+ packagePolicy.inputs[0].config.artifact_manifest = { value: serializedManifest };
+ packagePoliciesToUpdate.push(packagePolicy);
} else {
- this.logger.debug(`No change in manifest version for package policy: ${id}`);
+ this.logger.debug(
+ `No change in manifest content for package policy: ${id}. Staying on old version`
+ );
}
} else {
- errors.push(
- new EndpointError(`Package Policy ${id} has no 'inputs[0].config'`, newPackagePolicy)
- );
+ this.logger.debug(`No change in manifest version for package policy: ${id}`);
}
+ } else {
+ errors.push(
+ new EndpointError(`Package Policy ${id} has no 'inputs[0].config'`, packagePolicy)
+ );
}
+ });
+
+ // Split updates in batches with batch size: packagerTaskPackagePolicyUpdateBatchSize
+ const updateBatches = chunk(
+ packagePoliciesToUpdate,
+ this.packagerTaskPackagePolicyUpdateBatchSize
);
+ for (const currentBatch of updateBatches) {
+ const response = await this.packagePolicyService.bulkUpdate(
+ this.savedObjectsClient,
+ this.esClient,
+ currentBatch
+ );
+
+ // Parse errors
+ if (!isEmpty(response.failedPolicies)) {
+ errors.push(
+ ...response.failedPolicies.map((failedPolicy) => {
+ if (failedPolicy.error instanceof Error) {
+ return failedPolicy.error;
+ } else {
+ return new Error(failedPolicy.error.message);
+ }
+ })
+ );
+ }
+ // Log success updates
+ for (const updatedPolicy of response.updatedPolicies || []) {
+ this.logger.debug(
+ `Updated package policy ${
+ updatedPolicy.id
+ } with manifest version ${manifest.getSemanticVersion()}`
+ );
+ }
+ }
+
return errors;
}
@@ -583,20 +664,29 @@ export class ManifestManager {
this.logger.info(`Committed manifest ${manifest.getSemanticVersion()}`);
}
- private async listEndpointPolicies(page: number) {
+ private async listEndpointPolicies(page: number, perPage: number) {
return this.packagePolicyService.list(this.savedObjectsClient, {
page,
- perPage: 20,
+ perPage,
kuery: 'ingest-package-policies.package.name:endpoint',
});
}
- private async listEndpointPolicyIds(page: number) {
- return this.packagePolicyService.listIds(this.savedObjectsClient, {
- page,
- perPage: 20,
- kuery: 'ingest-package-policies.package.name:endpoint',
- });
+ private async listEndpointPolicyIds() {
+ const allPolicyIds: string[] = [];
+ await iterateAllListItems(
+ (page, perPage) => {
+ return this.packagePolicyService.listIds(this.savedObjectsClient, {
+ page,
+ perPage,
+ kuery: 'ingest-package-policies.package.name:endpoint',
+ });
+ },
+ (packagePolicyIdsBatch) => {
+ allPolicyIds.push(...packagePolicyIdsBatch);
+ }
+ );
+ return allPolicyIds;
}
public getArtifactsClient(): EndpointArtifactClientInterface {
@@ -635,6 +725,7 @@ export class ManifestManager {
}
const badArtifacts = [];
+ const badArtifactIds = [];
const manifestArtifactsIds = manifest
.getAllArtifacts()
@@ -646,6 +737,7 @@ export class ManifestManager {
if (!isArtifactInManifest) {
badArtifacts.push(fleetArtifact);
+ badArtifactIds.push(artifactId);
}
}
@@ -657,16 +749,7 @@ export class ManifestManager {
new EndpointError(`Cleaning up ${badArtifacts.length} orphan artifacts`, badArtifacts)
);
- await pMap(
- badArtifacts,
- async (badArtifact) => this.artifactClient.deleteArtifact(getArtifactId(badArtifact)),
- {
- concurrency: 5,
- /** When set to false, instead of stopping when a promise rejects, it will wait for all the promises to
- * settle and then reject with an aggregated error containing all the errors from the rejected promises. */
- stopOnError: false,
- }
- );
+ await this.artifactClient.bulkDeleteArtifacts(badArtifactIds);
this.logger.info(`All orphan artifacts has been removed successfully`);
} catch (error) {
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/mocks.ts
index 3d7b7b92f90c4..2acfa7b7b4794 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/mocks.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/mocks.ts
@@ -62,6 +62,9 @@ export const createEndpointArtifactClientMock = (
listArtifacts: jest.fn((...args) => endpointArtifactClientMocked.listArtifacts(...args)),
getArtifact: jest.fn((...args) => endpointArtifactClientMocked.getArtifact(...args)),
deleteArtifact: jest.fn((...args) => endpointArtifactClientMocked.deleteArtifact(...args)),
+ bulkDeleteArtifacts: jest.fn(async (...args) =>
+ endpointArtifactClientMocked.bulkDeleteArtifacts(...args)
+ ),
_esClient: esClient,
};
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts
index 0c64c496e5611..92180b723703f 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/api/get_prebuilt_rules_status/get_prebuilt_rules_status_route.ts
@@ -38,7 +38,7 @@ export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter
ruleAssetsClient,
ruleObjectsClient,
});
- const { currentRules, installableRules, upgradeableRules } =
+ const { currentRules, installableRules, upgradeableRules, totalAvailableRules } =
getVersionBuckets(ruleVersionsMap);
const body: GetPrebuiltRulesStatusResponseBody = {
@@ -46,6 +46,7 @@ export const getPrebuiltRulesStatusRoute = (router: SecuritySolutionPluginRouter
num_prebuilt_rules_installed: currentRules.length,
num_prebuilt_rules_to_install: installableRules.length,
num_prebuilt_rules_to_upgrade: upgradeableRules.length,
+ num_prebuilt_rules_total_in_package: totalAvailableRules.length,
},
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_versions/get_version_buckets.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_versions/get_version_buckets.ts
index 96e1f76248da6..754d0ec4330e2 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_versions/get_version_buckets.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/prebuilt_rules/model/rule_versions/get_version_buckets.ts
@@ -31,14 +31,25 @@ export interface VersionBuckets {
*/
target: PrebuiltRuleAsset;
}>;
+ /**
+ * All available rules
+ * (installed and not installed)
+ */
+ totalAvailableRules: PrebuiltRuleAsset[];
}
export const getVersionBuckets = (ruleVersionsMap: Map): VersionBuckets => {
const currentRules: RuleResponse[] = [];
const installableRules: PrebuiltRuleAsset[] = [];
+ const totalAvailableRules: PrebuiltRuleAsset[] = [];
const upgradeableRules: VersionBuckets['upgradeableRules'] = [];
ruleVersionsMap.forEach(({ current, target }) => {
+ if (target != null) {
+ // If this rule is available in the package
+ totalAvailableRules.push(target);
+ }
+
if (current != null) {
// If this rule is installed
currentRules.push(current);
@@ -62,5 +73,6 @@ export const getVersionBuckets = (ruleVersionsMap: Map): V
currentRules,
installableRules,
upgradeableRules,
+ totalAvailableRules,
};
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts
index 9f80623cd7cc7..f7c865605dc3d 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/register_routes.ts
@@ -25,7 +25,7 @@ import { patchRuleRoute } from './rules/patch_rule/route';
import { readRuleRoute } from './rules/read_rule/route';
import { updateRuleRoute } from './rules/update_rule/route';
import { readTagsRoute } from './tags/read_tags/route';
-import { getRulesDashboardDataRoute } from './rules/dashboard/route';
+import { getCoverageOverviewRoute } from './rules/coverage_overview/route';
export const registerRuleManagementRoutes = (
router: SecuritySolutionPluginRouter,
@@ -64,6 +64,6 @@ export const registerRuleManagementRoutes = (
// Rules dashboard
if (config.experimentalFeatures.detectionsCoverageOverview) {
- getRulesDashboardDataRoute();
+ getCoverageOverviewRoute(router);
}
};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/handle_coverage_overview_request.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/handle_coverage_overview_request.test.ts
new file mode 100644
index 0000000000000..d80057ffdef90
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/handle_coverage_overview_request.test.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { Rule } from '@kbn/alerting-plugin/common';
+import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks';
+import { handleCoverageOverviewRequest } from './handle_coverage_overview_request';
+
+describe('handleCoverageOverviewRequest', () => {
+ let rulesClient: ReturnType;
+
+ beforeEach(() => {
+ rulesClient = rulesClientMock.create();
+ });
+
+ it('does not request more than 10k', async () => {
+ rulesClient.find
+ .mockResolvedValueOnce({
+ total: 25555,
+ page: 1,
+ perPage: 10000,
+ data: generateRules(10000),
+ })
+ .mockResolvedValueOnce({
+ total: 25555,
+ page: 2,
+ perPage: 10000,
+ data: generateRules(10000),
+ })
+ .mockResolvedValueOnce({
+ total: 25555,
+ page: 3,
+ perPage: 10000,
+ data: generateRules(10000),
+ });
+
+ await handleCoverageOverviewRequest({
+ params: {},
+ deps: {
+ rulesClient,
+ },
+ });
+
+ expect(rulesClient.find).toHaveBeenCalledTimes(1);
+ expect(rulesClient.find).toHaveBeenCalledWith({
+ options: expect.objectContaining({
+ page: 1,
+ perPage: 10000,
+ }),
+ });
+ });
+});
+
+function generateRules(count: number): Rule[] {
+ const result: Rule[] = [];
+
+ for (let i = 1; i <= count; ++i) {
+ result.push({
+ name: `rule ${i}`,
+ enabled: false,
+ params: {},
+ } as Rule);
+ }
+
+ return result;
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/handle_coverage_overview_request.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/handle_coverage_overview_request.ts
new file mode 100644
index 0000000000000..101f4c190bb3e
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/handle_coverage_overview_request.ts
@@ -0,0 +1,126 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { SanitizedRule } from '@kbn/alerting-plugin/common';
+import type { RulesClient } from '@kbn/alerting-plugin/server';
+import { convertRulesFilterToKQL } from '../../../../../../../common/utils/kql';
+import type { CoverageOverviewRequestBody } from '../../../../../../../common/detection_engine/rule_management/api/rules/coverage_overview/request_schema';
+import {
+ CoverageOverviewRuleSource,
+ CoverageOverviewRuleActivity,
+} from '../../../../../../../common/detection_engine/rule_management/api/rules/coverage_overview/request_schema';
+import type { CoverageOverviewResponse } from '../../../../../../../common/detection_engine/rule_management/api/rules/coverage_overview/response_schema';
+import type { RuleParams } from '../../../../rule_schema';
+
+type CoverageOverviewRuleParams = Pick;
+
+interface CoverageOverviewRouteDependencies {
+ rulesClient: RulesClient;
+}
+
+interface HandleCoverageOverviewRequestArgs {
+ params: CoverageOverviewRequestBody;
+ deps: CoverageOverviewRouteDependencies;
+}
+
+export async function handleCoverageOverviewRequest({
+ params: { filter },
+ deps: { rulesClient },
+}: HandleCoverageOverviewRequestArgs): Promise {
+ const kqlFilter = filter
+ ? convertRulesFilterToKQL({
+ filter: filter.search_term,
+ showCustomRules: filter.source?.includes(CoverageOverviewRuleSource.Custom) ?? false,
+ showElasticRules: filter.source?.includes(CoverageOverviewRuleSource.Prebuilt) ?? false,
+ enabled: filter.activity?.includes(CoverageOverviewRuleActivity.Disabled)
+ ? false
+ : filter.activity?.includes(CoverageOverviewRuleActivity.Enabled)
+ ? true
+ : undefined,
+ })
+ : undefined;
+
+ // rulesClient.find uses ES Search API to fetch the rules. It has some limitations when the number of rules exceeds
+ // index.max_result_window (set to 10K by default) Kibana fails. A proper way to handle it is via ES PIT API.
+ // This way the endpoint handles max 10K rules for now while support for the higher number of rules will be addressed
+ // in https://github.com/elastic/kibana/issues/160698
+ const rules = await rulesClient.find({
+ options: {
+ filter: kqlFilter,
+ fields: ['name', 'enabled', 'params.threat'],
+ page: 1,
+ perPage: 10000,
+ },
+ });
+
+ return rules.data.reduce(appendRuleToResponse, {
+ coverage: {},
+ unmapped_rule_ids: [],
+ rules_data: {},
+ } as CoverageOverviewResponse);
+}
+
+/**
+ * Extracts rule's MITRE ATT&CK™ tactics, techniques and subtechniques
+ *
+ * @returns an array of MITRE ATT&CK™ tactics, techniques and subtechniques
+ */
+function extractRuleMitreCategories(rule: SanitizedRule): string[] {
+ if (!rule.params.threat) {
+ return [];
+ }
+
+ // avoid duplications just in case data isn't valid in ES
+ const categories = new Set();
+
+ for (const threatItem of rule.params.threat) {
+ if (threatItem.framework !== 'MITRE ATT&CK') {
+ // eslint-disable-next-line no-continue
+ continue;
+ }
+
+ categories.add(threatItem.tactic.id);
+
+ for (const technique of threatItem.technique ?? []) {
+ categories.add(technique.id);
+
+ for (const subtechnique of technique.subtechnique ?? []) {
+ categories.add(subtechnique.id);
+ }
+ }
+ }
+
+ return Array.from(categories);
+}
+
+function appendRuleToResponse(
+ response: CoverageOverviewResponse,
+ rule: SanitizedRule
+): CoverageOverviewResponse {
+ const categories = extractRuleMitreCategories(rule);
+
+ for (const category of categories) {
+ if (!response.coverage[category]) {
+ response.coverage[category] = [rule.id];
+ } else {
+ response.coverage[category].push(rule.id);
+ }
+ }
+
+ if (categories.length === 0) {
+ response.unmapped_rule_ids.push(rule.id);
+ }
+
+ response.rules_data[rule.id] = {
+ name: rule.name,
+ activity: rule.enabled
+ ? CoverageOverviewRuleActivity.Enabled
+ : CoverageOverviewRuleActivity.Disabled,
+ };
+
+ return response;
+}
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/route.ts
new file mode 100644
index 0000000000000..989d1a762cdc2
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/coverage_overview/route.ts
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { transformError } from '@kbn/securitysolution-es-utils';
+import { CoverageOverviewRequestBody } from '../../../../../../../common/detection_engine/rule_management/api/rules/coverage_overview/request_schema';
+import type { SecuritySolutionPluginRouter } from '../../../../../../types';
+import { RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL } from '../../../../../../../common/detection_engine/rule_management/api/urls';
+import { buildSiemResponse } from '../../../../routes/utils';
+import { buildRouteValidation } from '../../../../../../utils/build_validation/route_validation';
+import { handleCoverageOverviewRequest } from './handle_coverage_overview_request';
+
+export const getCoverageOverviewRoute = (router: SecuritySolutionPluginRouter) => {
+ router.post(
+ {
+ path: RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL,
+ validate: {
+ body: buildRouteValidation(CoverageOverviewRequestBody),
+ },
+ options: {
+ tags: ['access:securitySolution'],
+ },
+ },
+ async (context, request, response) => {
+ const siemResponse = buildSiemResponse(response);
+
+ try {
+ const ctx = await context.resolve(['alerting']);
+
+ const responseData = await handleCoverageOverviewRequest({
+ params: request.body,
+ deps: { rulesClient: ctx.alerting.getRulesClient() },
+ });
+
+ return response.ok({
+ body: responseData,
+ });
+ } catch (err) {
+ const error = transformError(err);
+
+ return siemResponse.error({
+ body: error.message,
+ statusCode: error.statusCode,
+ });
+ }
+ }
+ );
+};
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts
index 6ef0a2e2e9525..ee5d6082a9fdb 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/search/get_existing_prepackaged_rules.ts
@@ -6,20 +6,22 @@
*/
import type { RulesClient } from '@kbn/alerting-plugin/server';
+import {
+ KQL_FILTER_IMMUTABLE_RULES,
+ KQL_FILTER_MUTABLE_RULES,
+} from '../../../../../../common/utils/kql';
import { withSecuritySpan } from '../../../../../utils/with_security_span';
import { findRules } from './find_rules';
import type { RuleAlertType } from '../../../rule_schema';
export const MAX_PREBUILT_RULES_COUNT = 10_000;
-export const FILTER_NON_PREPACKED_RULES = 'alert.attributes.params.immutable: false';
-export const FILTER_PREPACKED_RULES = 'alert.attributes.params.immutable: true';
export const getNonPackagedRulesCount = async ({
rulesClient,
}: {
rulesClient: RulesClient;
}): Promise => {
- return getRulesCount({ rulesClient, filter: FILTER_NON_PREPACKED_RULES });
+ return getRulesCount({ rulesClient, filter: KQL_FILTER_MUTABLE_RULES });
};
export const getRulesCount = async ({
@@ -71,7 +73,7 @@ export const getNonPackagedRules = async ({
}): Promise => {
return getRules({
rulesClient,
- filter: FILTER_NON_PREPACKED_RULES,
+ filter: KQL_FILTER_MUTABLE_RULES,
});
};
@@ -82,6 +84,6 @@ export const getExistingPrepackagedRules = async ({
}): Promise => {
return getRules({
rulesClient,
- filter: FILTER_PREPACKED_RULES,
+ filter: KQL_FILTER_IMMUTABLE_RULES,
});
};
diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts
index f3a6ace3f2acd..badc35993ab03 100644
--- a/x-pack/plugins/security_solution/server/plugin.ts
+++ b/x-pack/plugins/security_solution/server/plugin.ts
@@ -459,6 +459,8 @@ export class Plugin implements ISecuritySolutionPlugin {
packagePolicyService: plugins.fleet.packagePolicyService,
logger,
experimentalFeatures: config.experimentalFeatures,
+ packagerTaskPackagePolicyUpdateBatchSize: config.packagerTaskPackagePolicyUpdateBatchSize,
+ esClient: core.elasticsearch.client.asInternalUser,
});
// Migrate artifacts to fleet and then start the minifest task after that is done
diff --git a/x-pack/plugins/serverless_search/common/types/index.ts b/x-pack/plugins/serverless_search/common/types/index.ts
index 13c8fc566defd..c4dac1508374e 100644
--- a/x-pack/plugins/serverless_search/common/types/index.ts
+++ b/x-pack/plugins/serverless_search/common/types/index.ts
@@ -11,3 +11,12 @@ export interface CreateAPIKeyArgs {
name: string;
role_descriptors?: Record;
}
+
+export interface IndexData {
+ name: string;
+ count: number;
+}
+
+export interface FetchIndicesResult {
+ indices: IndexData[];
+}
diff --git a/x-pack/plugins/serverless_search/common/utils/is_not_nullish.ts b/x-pack/plugins/serverless_search/common/utils/is_not_nullish.ts
new file mode 100644
index 0000000000000..d492dad5d52c2
--- /dev/null
+++ b/x-pack/plugins/serverless_search/common/utils/is_not_nullish.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export function isNotNullish(value: T | null | undefined): value is T {
+ return value !== null && value !== undefined;
+}
diff --git a/x-pack/plugins/serverless_search/public/application/components/indexing_api.tsx b/x-pack/plugins/serverless_search/public/application/components/indexing_api.tsx
index adef923902c59..eb34e345be142 100644
--- a/x-pack/plugins/serverless_search/public/application/components/indexing_api.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/indexing_api.tsx
@@ -5,23 +5,33 @@
* 2.0.
*/
-import React, { useState } from 'react';
+import React, { useMemo, useState } from 'react';
import {
+ EuiCallOut,
+ EuiComboBox,
+ EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
+ EuiFormRow,
EuiLink,
EuiPageTemplate,
EuiSpacer,
+ EuiStat,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
+import { useQuery } from '@tanstack/react-query';
+import { IndexData, FetchIndicesResult } from '../../../common/types';
+import { FETCH_INDICES_PATH } from '../routes';
+import { API_KEY_PLACEHOLDER, ELASTICSEARCH_URL_PLACEHOLDER } from '../constants';
+import { useKibanaServices } from '../hooks/use_kibana';
import { CodeBox } from './code_box';
import { javascriptDefinition } from './languages/javascript';
import { languageDefinitions } from './languages/languages';
-import { LanguageDefinition } from './languages/types';
+import { LanguageDefinition, LanguageDefinitionSnippetArguments } from './languages/types';
import { OverviewPanel } from './overview_panels/overview_panel';
import { LanguageClientPanel } from './overview_panels/language_client_panel';
@@ -48,9 +58,88 @@ const NoIndicesContent = () => (
>
);
+interface IndicesContentProps {
+ indices: IndexData[];
+ isLoading: boolean;
+ onChange: (selectedOptions: Array>) => void;
+ selectedIndex?: IndexData;
+ setSearchValue: (searchValue?: string) => void;
+}
+const IndicesContent = ({
+ indices,
+ isLoading,
+ onChange,
+ selectedIndex,
+ setSearchValue,
+}: IndicesContentProps) => {
+ const toOption = (index: IndexData) => ({ label: index.name, value: index });
+ const options: Array> = indices.map(toOption);
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
export const ElasticsearchIndexingApi = () => {
+ const { cloud, http } = useKibanaServices();
const [selectedLanguage, setSelectedLanguage] =
useState(javascriptDefinition);
+ const [indexSearchQuery, setIndexSearchQuery] = useState(undefined);
+ const [selectedIndex, setSelectedIndex] = useState(undefined);
+ const elasticsearchURL = useMemo(() => {
+ return cloud?.elasticsearchUrl ?? ELASTICSEARCH_URL_PLACEHOLDER;
+ }, [cloud]);
+ const { data, isLoading, isError } = useQuery({
+ queryKey: ['indices', { searchQuery: indexSearchQuery }],
+ queryFn: async () => {
+ const query = {
+ search_query: indexSearchQuery || null,
+ };
+ const result = await http.get(FETCH_INDICES_PATH, { query });
+ return result;
+ },
+ });
+
+ const codeSnippetArguments: LanguageDefinitionSnippetArguments = {
+ url: elasticsearchURL,
+ apiKey: API_KEY_PLACEHOLDER,
+ indexName: selectedIndex?.name,
+ };
+ const showNoIndices = !isLoading && data?.indices?.length === 0 && indexSearchQuery === undefined;
return (
@@ -67,6 +156,17 @@ export const ElasticsearchIndexingApi = () => {
)}
bottomBorder="extended"
/>
+ {isError && (
+
+
+
+ )}
{
>
}
+ links={
+ showNoIndices
+ ? undefined
+ : [
+ {
+ label: i18n.translate(
+ 'xpack.serverlessSearch.content.indexingApi.ingestDocsLink',
+ { defaultMessage: 'Ingestion documentation' }
+ ),
+ href: '#', // TODO: get doc links ?
+ },
+ ]
+ }
>
-
+ {showNoIndices ? (
+
+ ) : (
+ {
+ setSelectedIndex(options?.[0]?.value);
+ }}
+ setSearchValue={setIndexSearchQuery}
+ selectedIndex={selectedIndex}
+ />
+ )}
diff --git a/x-pack/plugins/serverless_search/public/application/components/languages/console.ts b/x-pack/plugins/serverless_search/public/application/components/languages/console.ts
index 08571e6d87173..c2fceaae4f85b 100644
--- a/x-pack/plugins/serverless_search/public/application/components/languages/console.ts
+++ b/x-pack/plugins/serverless_search/public/application/components/languages/console.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { INDEX_NAME_PLACEHOLDER } from '../../constants';
import { LanguageDefinition } from './types';
export const consoleDefinition: Partial = {
@@ -29,4 +30,8 @@ export const consoleDefinition: Partial = {
{"name": "Brave New World", "author": "Aldous Huxley", "release_date": "1932-06-01", "page_count": 268}
{ "index" : { "_index" : "books" } }
{"name": "The Handmaid's Tale", "author": "Margaret Atwood", "release_date": "1985-06-01", "page_count": 311}`,
+ ingestDataIndex: ({ indexName }) => `POST _bulk?pretty
+ { "index" : { "_index" : "${indexName ?? INDEX_NAME_PLACEHOLDER}" } }
+ {"name": "foo", "title": "bar"}
+`,
};
diff --git a/x-pack/plugins/serverless_search/public/application/components/languages/curl.ts b/x-pack/plugins/serverless_search/public/application/components/languages/curl.ts
index 41c18a9470dcb..cc98b35c87696 100644
--- a/x-pack/plugins/serverless_search/public/application/components/languages/curl.ts
+++ b/x-pack/plugins/serverless_search/public/application/components/languages/curl.ts
@@ -43,6 +43,13 @@ export API_KEY="${apiKey}"`,
{ "index" : { "_index" : "books" } }
{"name": "The Handmaid'"'"'s Tale", "author": "Margaret Atwood", "release_date": "1985-06-01", "page_count": 311}
'`,
+ ingestDataIndex: ({ apiKey, url, indexName }) => `curl -X POST ${url}/_bulk?pretty \\
+ -H "Authorization: ApiKey ${apiKey}" \\
+ -H "Content-Type: application/json" \\
+ -d'
+{ "index" : { "_index" : "${indexName ?? 'index_name'}" } }
+{"name": "foo", "title": "bar" }
+`,
installClient: `# if cURL is not already installed on your system
# then install it with the package manager of your choice
diff --git a/x-pack/plugins/serverless_search/public/application/components/languages/javascript.ts b/x-pack/plugins/serverless_search/public/application/components/languages/javascript.ts
index 09187c68670ee..9a3978081e404 100644
--- a/x-pack/plugins/serverless_search/public/application/components/languages/javascript.ts
+++ b/x-pack/plugins/serverless_search/public/application/components/languages/javascript.ts
@@ -59,6 +59,30 @@ bytes: 293,
aborted: false
}
*/`,
+ ingestDataIndex: ({
+ apiKey,
+ url,
+ indexName,
+ }) => `const { Client } = require('@elastic/elasticsearch');
+const client = new Client({
+ node: '${url}',
+ auth: {
+ apiKey: '${apiKey}'
+ }
+});
+const dataset = [
+ {'name': 'foo', 'title': 'bar'},
+];
+
+// Index with the bulk helper
+const result = await client.helpers.bulk({
+ datasource: dataset,
+ onDocument (doc) {
+ return { index: { _index: '${indexName ?? 'index_name'}' }};
+ }
+});
+console.log(result);
+`,
installClient: 'npm install @elastic/elasticsearch@8',
name: i18n.translate('xpack.serverlessSearch.languages.javascript', {
defaultMessage: 'JavaScript / Node.js',
diff --git a/x-pack/plugins/serverless_search/public/application/components/languages/ruby.ts b/x-pack/plugins/serverless_search/public/application/components/languages/ruby.ts
index c13ae7d5ced66..b6b8ce3c24428 100644
--- a/x-pack/plugins/serverless_search/public/application/components/languages/ruby.ts
+++ b/x-pack/plugins/serverless_search/public/application/components/languages/ruby.ts
@@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import { docLinks } from '../../../../common/doc_links';
+import { INDEX_NAME_PLACEHOLDER } from '../../constants';
import { LanguageDefinition, Languages } from './types';
export const rubyDefinition: LanguageDefinition = {
@@ -31,6 +32,18 @@ export const rubyDefinition: LanguageDefinition = {
{ index: { _index: 'books', data: {name: "The Handmaid's Tale", "author": "Margaret Atwood", "release_date": "1985-06-01", "page_count": 311} } }
]
client.bulk(body: documents)`,
+ ingestDataIndex: ({ apiKey, url, indexName }) => `client = ElasticsearchServerless::Client.new(
+ api_key: '${apiKey}',
+ url: '${url}'
+)
+
+documents = [
+ { index: { _index: '${
+ indexName ?? INDEX_NAME_PLACEHOLDER
+ }', data: {name: "foo", "title": "bar"} } },
+]
+client.bulk(body: documents)
+`,
installClient: `# Requires Ruby version 3.0 or higher
# From the project's root directory:$ gem build elasticsearch-serverless.gemspec
diff --git a/x-pack/plugins/serverless_search/public/application/components/languages/types.ts b/x-pack/plugins/serverless_search/public/application/components/languages/types.ts
index 5e2fe63681389..7849b800fc1a0 100644
--- a/x-pack/plugins/serverless_search/public/application/components/languages/types.ts
+++ b/x-pack/plugins/serverless_search/public/application/components/languages/types.ts
@@ -21,6 +21,7 @@ export enum Languages {
export interface LanguageDefinitionSnippetArguments {
url: string;
apiKey: string;
+ indexName?: string;
}
type CodeSnippet = string | ((args: LanguageDefinitionSnippetArguments) => string);
@@ -33,6 +34,7 @@ export interface LanguageDefinition {
iconType: string;
id: Languages;
ingestData: CodeSnippet;
+ ingestDataIndex: CodeSnippet;
installClient: string;
languageStyling?: string;
name: string;
diff --git a/x-pack/plugins/serverless_search/public/application/components/overview.tsx b/x-pack/plugins/serverless_search/public/application/components/overview.tsx
index 59c0a0f2eca88..a683f64820785 100644
--- a/x-pack/plugins/serverless_search/public/application/components/overview.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/overview.tsx
@@ -23,6 +23,7 @@ import React, { useMemo, useState } from 'react';
import { docLinks } from '../../../common/doc_links';
import { PLUGIN_ID } from '../../../common';
import { useKibanaServices } from '../hooks/use_kibana';
+import { API_KEY_PLACEHOLDER, ELASTICSEARCH_URL_PLACEHOLDER } from '../constants';
import { CodeBox } from './code_box';
import { javascriptDefinition } from './languages/javascript';
import { languageDefinitions } from './languages/languages';
@@ -35,9 +36,6 @@ import { SelectClientPanel } from './overview_panels/select_client';
import { ApiKeyPanel } from './api_key/api_key';
import { LanguageClientPanel } from './overview_panels/language_client_panel';
-const ELASTICSEARCH_URL_PLACEHOLDER = 'https://your_deployment_url';
-const API_KEY_PLACEHOLDER = 'your_api_key';
-
export const ElasticsearchOverview = () => {
const [selectedLanguage, setSelectedLanguage] =
useState(javascriptDefinition);
diff --git a/x-pack/plugins/serverless_search/public/application/constants.ts b/x-pack/plugins/serverless_search/public/application/constants.ts
new file mode 100644
index 0000000000000..b0b122fa01b5c
--- /dev/null
+++ b/x-pack/plugins/serverless_search/public/application/constants.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const API_KEY_PLACEHOLDER = 'your_api_key';
+export const ELASTICSEARCH_URL_PLACEHOLDER = 'https://your_deployment_url';
+export const INDEX_NAME_PLACEHOLDER = 'index_name';
diff --git a/x-pack/plugins/serverless_search/public/application/routes.ts b/x-pack/plugins/serverless_search/public/application/routes.ts
index 1a933d8f01717..bace5c55d54e2 100644
--- a/x-pack/plugins/serverless_search/public/application/routes.ts
+++ b/x-pack/plugins/serverless_search/public/application/routes.ts
@@ -9,3 +9,4 @@ export const MANAGEMENT_API_KEYS = '/app/management/security/api_keys';
// Server Routes
export const CREATE_API_KEY_PATH = '/internal/security/api_key';
+export const FETCH_INDICES_PATH = '/internal/serverless_search/indices';
diff --git a/x-pack/plugins/serverless_search/server/lib/indices/fetch_indices.test.ts b/x-pack/plugins/serverless_search/server/lib/indices/fetch_indices.test.ts
new file mode 100644
index 0000000000000..da2a229340c98
--- /dev/null
+++ b/x-pack/plugins/serverless_search/server/lib/indices/fetch_indices.test.ts
@@ -0,0 +1,122 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { ByteSizeValue } from '@kbn/config-schema';
+import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
+import { fetchIndices } from './fetch_indices';
+
+describe('fetch indices lib functions', () => {
+ const mockClient = {
+ indices: {
+ get: jest.fn(),
+ stats: jest.fn(),
+ },
+ security: {
+ hasPrivileges: jest.fn(),
+ },
+ asInternalUser: {},
+ };
+
+ const regularIndexResponse = {
+ 'search-regular-index': {
+ aliases: {},
+ },
+ };
+
+ const regularIndexStatsResponse = {
+ indices: {
+ 'search-regular-index': {
+ health: 'green',
+ size: new ByteSizeValue(108000).toString(),
+ status: 'open',
+ total: {
+ docs: {
+ count: 100,
+ deleted: 0,
+ },
+ store: {
+ size_in_bytes: 108000,
+ },
+ },
+ uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
+ },
+ },
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('fetchIndices', () => {
+ it('should return regular index', async () => {
+ mockClient.indices.get.mockImplementation(() => ({
+ ...regularIndexResponse,
+ }));
+ mockClient.indices.stats.mockImplementation(() => regularIndexStatsResponse);
+
+ await expect(
+ fetchIndices(mockClient as unknown as ElasticsearchClient, 0, 20, 'search')
+ ).resolves.toEqual([{ count: 100, name: 'search-regular-index' }]);
+ expect(mockClient.indices.get).toHaveBeenCalledWith({
+ expand_wildcards: ['open'],
+ features: ['aliases', 'settings'],
+ index: '*search*',
+ });
+
+ expect(mockClient.indices.stats).toHaveBeenCalledWith({
+ index: ['search-regular-index'],
+ metric: ['docs'],
+ });
+ });
+
+ it('should not return hidden indices', async () => {
+ mockClient.indices.get.mockImplementation(() => ({
+ ...regularIndexResponse,
+ ['search-regular-index']: {
+ ...regularIndexResponse['search-regular-index'],
+ ...{ settings: { index: { hidden: 'true' } } },
+ },
+ }));
+ mockClient.indices.stats.mockImplementation(() => regularIndexStatsResponse);
+
+ await expect(
+ fetchIndices(mockClient as unknown as ElasticsearchClient, 0, 20)
+ ).resolves.toEqual([]);
+ expect(mockClient.indices.get).toHaveBeenCalledWith({
+ expand_wildcards: ['open'],
+ features: ['aliases', 'settings'],
+ index: '*',
+ });
+
+ expect(mockClient.indices.stats).not.toHaveBeenCalled();
+ });
+
+ it('should handle index missing in stats call', async () => {
+ const missingStatsResponse = {
+ indices: {
+ some_other_index: { ...regularIndexStatsResponse.indices['search-regular-index'] },
+ },
+ };
+
+ mockClient.indices.get.mockImplementationOnce(() => regularIndexResponse);
+ mockClient.indices.stats.mockImplementationOnce(() => missingStatsResponse);
+ // simulates when an index has been deleted after get indices call
+ // deleted index won't be present in the indices stats call response
+ await expect(
+ fetchIndices(mockClient as unknown as ElasticsearchClient, 0, 20, 'search')
+ ).resolves.toEqual([{ count: 0, name: 'search-regular-index' }]);
+ });
+
+ it('should return empty array when no index found', async () => {
+ mockClient.indices.get.mockImplementationOnce(() => ({}));
+ await expect(
+ fetchIndices(mockClient as unknown as ElasticsearchClient, 0, 20, 'search')
+ ).resolves.toEqual([]);
+ expect(mockClient.indices.stats).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/x-pack/plugins/serverless_search/server/lib/indices/fetch_indices.ts b/x-pack/plugins/serverless_search/server/lib/indices/fetch_indices.ts
new file mode 100644
index 0000000000000..9560bbe7f9bb3
--- /dev/null
+++ b/x-pack/plugins/serverless_search/server/lib/indices/fetch_indices.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
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { IndicesStatsIndicesStats } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
+import { isNotNullish } from '../../../common/utils/is_not_nullish';
+import { isHidden, isClosed } from '../../utils/index_utils';
+
+export async function fetchIndices(
+ client: ElasticsearchClient,
+ from: number,
+ size: number,
+ searchQuery?: string
+) {
+ const indexPattern = searchQuery ? `*${searchQuery}*` : '*';
+ const indexMatches = await client.indices.get({
+ expand_wildcards: ['open'],
+ // for better performance only compute settings of indices but not mappings
+ features: ['aliases', 'settings'],
+ index: indexPattern,
+ });
+ const indexNames = Object.keys(indexMatches).filter(
+ (indexName) =>
+ indexMatches[indexName] &&
+ !isHidden(indexMatches[indexName]) &&
+ !isClosed(indexMatches[indexName])
+ );
+ const indexNameSlice = indexNames.slice(from, from + size).filter(isNotNullish);
+ if (indexNameSlice.length === 0) {
+ return [];
+ }
+ const indexCounts = await fetchIndexCounts(client, indexNameSlice);
+ return indexNameSlice.map((name) => ({
+ name,
+ count: indexCounts[name]?.total?.docs?.count ?? 0,
+ }));
+}
+
+const fetchIndexCounts = async (
+ client: ElasticsearchClient,
+ indicesNames: string[]
+): Promise> => {
+ if (indicesNames.length === 0) {
+ return {};
+ }
+ const indexCounts: Record = {};
+ // batch calls in batches of 100 to prevent loading too much onto ES
+ for (let i = 0; i < indicesNames.length; i += 100) {
+ const stats = await client.indices.stats({
+ index: indicesNames.slice(i, i + 100),
+ metric: ['docs'],
+ });
+ Object.assign(indexCounts, stats.indices);
+ }
+ return indexCounts;
+};
diff --git a/x-pack/plugins/serverless_search/server/plugin.ts b/x-pack/plugins/serverless_search/server/plugin.ts
index caa53bcf0adb0..760e59f82199c 100644
--- a/x-pack/plugins/serverless_search/server/plugin.ts
+++ b/x-pack/plugins/serverless_search/server/plugin.ts
@@ -8,6 +8,7 @@
import { IRouter, Logger, PluginInitializerContext, Plugin, CoreSetup } from '@kbn/core/server';
import { SecurityPluginStart } from '@kbn/security-plugin/server';
import { registerApiKeyRoutes } from './routes/api_key_routes';
+import { registerIndicesRoutes } from './routes/indices_routes';
import { ServerlessSearchConfig } from './config';
import { ServerlessSearchPluginSetup, ServerlessSearchPluginStart } from './types';
@@ -41,6 +42,7 @@ export class ServerlessSearchPlugin
const dependencies = { logger: this.logger, router, security: this.security };
registerApiKeyRoutes(dependencies);
+ registerIndicesRoutes(dependencies);
});
return {};
}
diff --git a/x-pack/plugins/serverless_search/server/routes/indices_routes.ts b/x-pack/plugins/serverless_search/server/routes/indices_routes.ts
new file mode 100644
index 0000000000000..75f2d737ccc90
--- /dev/null
+++ b/x-pack/plugins/serverless_search/server/routes/indices_routes.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { schema } from '@kbn/config-schema';
+
+import { fetchIndices } from '../lib/indices/fetch_indices';
+import { RouteDependencies } from '../plugin';
+
+export const registerIndicesRoutes = ({ router, security }: RouteDependencies) => {
+ router.get(
+ {
+ path: '/internal/serverless_search/indices',
+ validate: {
+ query: schema.object({
+ from: schema.number({ defaultValue: 0, min: 0 }),
+ search_query: schema.maybe(schema.string()),
+ size: schema.number({ defaultValue: 20, min: 0 }),
+ }),
+ },
+ },
+ async (context, request, response) => {
+ const client = (await context.core).elasticsearch.client.asCurrentUser;
+ const user = security.authc.getCurrentUser(request);
+
+ if (!user) {
+ return response.customError({
+ statusCode: 502,
+ body: 'Could not retrieve current user, security plugin is not ready',
+ });
+ }
+
+ const { from, size, search_query: searchQuery } = request.query;
+
+ const indices = await fetchIndices(client, from, size, searchQuery);
+ return response.ok({
+ body: {
+ indices,
+ },
+ headers: { 'content-type': 'application/json' },
+ });
+ }
+ );
+};
diff --git a/x-pack/plugins/serverless_search/server/utils/index_utils.ts b/x-pack/plugins/serverless_search/server/utils/index_utils.ts
new file mode 100644
index 0000000000000..043bb80d7f8d0
--- /dev/null
+++ b/x-pack/plugins/serverless_search/server/utils/index_utils.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { IndicesIndexState } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+
+export function isHidden(index: IndicesIndexState): boolean {
+ return index.settings?.index?.hidden === true || index.settings?.index?.hidden === 'true';
+}
+
+export function isClosed(index: IndicesIndexState): boolean {
+ return (
+ index.settings?.index?.verified_before_close === true ||
+ index.settings?.index?.verified_before_close === 'true'
+ );
+}
diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json
index c9cd5562ff562..e62623958886c 100644
--- a/x-pack/plugins/serverless_search/tsconfig.json
+++ b/x-pack/plugins/serverless_search/tsconfig.json
@@ -27,5 +27,6 @@
"@kbn/security-plugin",
"@kbn/cloud-plugin",
"@kbn/share-plugin",
+ "@kbn/core-elasticsearch-server",
]
}
diff --git a/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts b/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts
index 29afd67b7f6bb..65d466a5ab2f8 100644
--- a/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts
+++ b/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts
@@ -5,28 +5,18 @@
* 2.0.
*/
-import type { SimpleSavedObject } from '@kbn/core/public';
import {
- Locations,
MonitorFields,
ServiceLocationErrors,
SyntheticsMonitor,
SyntheticsMonitorSchedule,
} from '../runtime_types';
-export interface MonitorIdParam {
- monitorId: string;
-}
-
-export type DecryptedSyntheticsMonitorSavedObject = SimpleSavedObject & {
- updated_at: string;
-};
-
export interface TestNowResponse {
schedule: SyntheticsMonitorSchedule;
- locations: Locations;
+ locations: MonitorFields['locations'];
errors?: ServiceLocationErrors;
testRunId: string;
configId: string;
- monitor: MonitorFields;
+ monitor: SyntheticsMonitor;
}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts
index a0a50cb88fdb3..92488a8adbd66 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/getting_started/use_simple_monitor.ts
@@ -12,7 +12,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { useSyntheticsRefreshContext } from '../../contexts';
import { cleanMonitorListState, selectServiceLocationsState } from '../../state';
import { showSyncErrors } from '../monitors_page/management/show_sync_errors';
-import { fetchCreateMonitor } from '../../state';
+import { createGettingStartedMonitor } from '../../state';
import { DEFAULT_FIELDS } from '../../../../../common/constants/monitor_defaults';
import { ConfigKey } from '../../../../../common/constants/monitor_management';
import {
@@ -41,7 +41,7 @@ export const useSimpleMonitor = ({ monitorData }: { monitorData?: SimpleFormData
}
const { urls, locations } = monitorData;
- return fetchCreateMonitor({
+ return createGettingStartedMonitor({
monitor: {
...DEFAULT_FIELDS.browser,
'source.inline.script': `step('Go to ${urls}', async () => {
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode_flyout.tsx
index cfb9348954624..57806ba4fdbe0 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode_flyout.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_now_mode_flyout.tsx
@@ -20,13 +20,13 @@ import {
} from '@elastic/eui';
import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout';
-import { MonitorFields, ServiceLocationErrors } from '../../../../../common/runtime_types';
+import { ServiceLocationErrors, SyntheticsMonitor } from '../../../../../common/runtime_types';
import { TestNowMode } from './test_now_mode';
export interface TestRun {
id: string;
name: string;
- monitor: MonitorFields;
+ monitor: SyntheticsMonitor;
}
export function TestNowModeFlyout({
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts
index 936efc6ada6ca..73f6ec9f76a22 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/manual_test_runs/index.ts
@@ -19,7 +19,7 @@ import {
toggleTestNowFlyoutAction,
} from './actions';
import {
- Locations,
+ MonitorFields,
ScheduleUnit,
ServiceLocationErrors,
SyntheticsMonitorSchedule,
@@ -40,7 +40,7 @@ export interface ManualTestRun {
testRunId?: string;
status: TestRunStatus;
schedule: SyntheticsMonitorSchedule;
- locations: Locations;
+ locations: MonitorFields['locations'];
errors?: ServiceLocationErrors;
fetchError?: { name: string; message: string };
isTestNowFlyoutOpen: boolean;
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts
index 1f1eb7dd74976..47737966a5a12 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts
@@ -71,12 +71,14 @@ export const fetchUpsertMonitor = async ({
}
};
-export const fetchCreateMonitor = async ({
+export const createGettingStartedMonitor = async ({
monitor,
}: {
monitor: SyntheticsMonitor | EncryptedSyntheticsMonitor;
}): Promise<{ attributes: { errors: ServiceLocationErrors } } | SyntheticsMonitor> => {
- return await apiService.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS, monitor);
+ return await apiService.post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS, monitor, undefined, {
+ gettingStarted: true,
+ });
};
export const fetchMonitorFilters = async (): Promise => {
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts
index 4041df5a3862f..8865439e0dcc4 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts
@@ -137,4 +137,4 @@ export * from './actions';
export * from './effects';
export * from './selectors';
export * from './helpers';
-export { fetchDeleteMonitor, fetchUpsertMonitor, fetchCreateMonitor } from './api';
+export { fetchDeleteMonitor, fetchUpsertMonitor, createGettingStartedMonitor } from './api';
diff --git a/x-pack/plugins/synthetics/server/routes/certs/get_certificates.ts b/x-pack/plugins/synthetics/server/routes/certs/get_certificates.ts
index 0054e4cf9d73b..982d9f60f90ff 100644
--- a/x-pack/plugins/synthetics/server/routes/certs/get_certificates.ts
+++ b/x-pack/plugins/synthetics/server/routes/certs/get_certificates.ts
@@ -48,6 +48,15 @@ export const getSyntheticsCertsRoute: SyntheticsRestApiRouteFactory<
filter: `${monitorAttributes}.${AlertConfigKey.STATUS_ENABLED}: true`,
});
+ if (monitors.length === 0) {
+ return {
+ data: {
+ certs: [],
+ total: 0,
+ },
+ };
+ }
+
const { enabledMonitorQueryIds } = await processMonitors(
monitors,
server,
diff --git a/x-pack/plugins/synthetics/server/routes/default_alerts/enable_default_alert.ts b/x-pack/plugins/synthetics/server/routes/default_alerts/enable_default_alert.ts
index f27abba13e822..49481a4347a1d 100644
--- a/x-pack/plugins/synthetics/server/routes/default_alerts/enable_default_alert.ts
+++ b/x-pack/plugins/synthetics/server/routes/default_alerts/enable_default_alert.ts
@@ -17,21 +17,6 @@ export const enableDefaultAlertingRoute: SyntheticsRestApiRouteFactory = () => (
handler: async ({ context, server, savedObjectsClient }): Promise => {
const defaultAlertService = new DefaultAlertService(context, server, savedObjectsClient);
- const [statusRule, tlsRule] = await Promise.allSettled([
- defaultAlertService.setupStatusRule(),
- defaultAlertService.setupTlsRule(),
- ]);
-
- if (statusRule.status === 'rejected') {
- throw statusRule.reason;
- }
- if (tlsRule.status === 'rejected') {
- throw tlsRule.reason;
- }
-
- return {
- statusRule: statusRule.status === 'fulfilled' ? statusRule.value : null,
- tlsRule: tlsRule.status === 'fulfilled' ? tlsRule.value : null,
- };
+ return defaultAlertService.setupDefaultAlerts();
},
});
diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts
index fac69c862db26..8ff63923aba16 100644
--- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts
+++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor.ts
@@ -15,6 +15,7 @@ import {
import { isValidNamespace } from '@kbn/fleet-plugin/common';
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import { DefaultAlertService } from '../default_alerts/default_alert_service';
+import { triggerTestNow } from '../synthetics_service/test_now_monitor';
import { SyntheticsServerSetup } from '../../types';
import { RouteContext, SyntheticsRestApiRouteFactory } from '../types';
import { syntheticsMonitorType } from '../../../common/types/saved_objects';
@@ -45,11 +46,12 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
query: schema.object({
id: schema.maybe(schema.string()),
preserve_namespace: schema.maybe(schema.boolean()),
+ gettingStarted: schema.maybe(schema.boolean()),
}),
},
writeAccess: true,
handler: async (routeContext): Promise => {
- const { context, request, response, savedObjectsClient, server } = routeContext;
+ const { request, response, savedObjectsClient, server } = routeContext;
// usually id is auto generated, but this is useful for testing
const { id } = request.query;
@@ -91,20 +93,8 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
},
});
}
-
- try {
- // we do this async, so we don't block the user, error handling will be done on the UI via separate api
- const defaultAlertService = new DefaultAlertService(context, server, savedObjectsClient);
- defaultAlertService.setupDefaultAlerts().then(() => {
- server.logger.debug(
- `Successfully created default alert for monitor: ${newMonitor.attributes.name}`
- );
- });
- } catch (e) {
- server.logger.error(
- `Error creating default alert: ${e} for monitor: ${newMonitor.attributes.name}`
- );
- }
+ initDefaultAlerts(newMonitor.attributes.name, routeContext);
+ setupGettingStarted(newMonitor.id, routeContext);
return response.ok({ body: newMonitor });
} catch (getErr) {
@@ -304,3 +294,34 @@ export const getMonitorNamespace = (
}
return namespace;
};
+
+const initDefaultAlerts = (name: string, routeContext: RouteContext) => {
+ const { server, savedObjectsClient, context } = routeContext;
+ try {
+ // we do this async, so we don't block the user, error handling will be done on the UI via separate api
+ const defaultAlertService = new DefaultAlertService(context, server, savedObjectsClient);
+ defaultAlertService.setupDefaultAlerts().then(() => {
+ server.logger.debug(`Successfully created default alert for monitor: ${name}`);
+ });
+ } catch (e) {
+ server.logger.error(`Error creating default alert: ${e} for monitor: ${name}`);
+ }
+};
+
+const setupGettingStarted = (configId: string, routeContext: RouteContext) => {
+ const { server, request } = routeContext;
+
+ try {
+ const { gettingStarted } = request.query;
+
+ if (gettingStarted) {
+ // ignore await, since we don't want to block the response
+ triggerTestNow(configId, routeContext).then(() => {
+ server.logger.debug(`Successfully triggered test for monitor: ${configId}`);
+ });
+ }
+ } catch (e) {
+ server.logger.info(`Error triggering test for getting started monitor: ${configId}`);
+ server.logger.error(e);
+ }
+};
diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/inspect_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/inspect_monitor.ts
index ca8fd63b2745d..b97965059cba0 100644
--- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/inspect_monitor.ts
+++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/inspect_monitor.ts
@@ -26,7 +26,6 @@ export const inspectSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () =
body: schema.any(),
query: schema.object({
id: schema.maybe(schema.string()),
- preserve_namespace: schema.maybe(schema.boolean()),
hideParams: schema.maybe(schema.boolean()),
}),
},
@@ -73,8 +72,6 @@ export const inspectSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () =
const result = await syntheticsMonitorClient.inspectMonitor(
{ monitor: monitorWithNamespace as MonitorFields, id: newMonitorId },
- request,
- savedObjectsClient,
privateLocations,
spaceId,
hideParams,
diff --git a/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts b/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts
index 0300c81ed3e8f..22fc903055eba 100644
--- a/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts
+++ b/x-pack/plugins/synthetics/server/routes/synthetics_service/test_now_monitor.ts
@@ -6,8 +6,7 @@
*/
import { schema } from '@kbn/config-schema';
import { v4 as uuidv4 } from 'uuid';
-import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
-import { SyntheticsRestApiRouteFactory } from '../types';
+import { RouteContext, SyntheticsRestApiRouteFactory } from '../types';
import { syntheticsMonitorType } from '../../../common/types/saved_objects';
import { TestNowResponse } from '../../../common/types';
import {
@@ -18,7 +17,7 @@ import {
import { SYNTHETICS_API_URLS } from '../../../common/constants';
import { normalizeSecrets } from '../../synthetics_service/utils/secrets';
-export const testNowMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
+export const testNowMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
method: 'GET',
path: SYNTHETICS_API_URLS.TRIGGER_MONITOR + '/{monitorId}',
validate: {
@@ -26,54 +25,59 @@ export const testNowMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
monitorId: schema.string({ minLength: 1, maxLength: 1024 }),
}),
},
- handler: async ({ request, server, syntheticsMonitorClient }): Promise => {
- const { monitorId } = request.params;
- const encryptedClient = server.encryptedSavedObjects.getClient();
-
- const monitorWithSecrets =
- await encryptedClient.getDecryptedAsInternalUser(
- syntheticsMonitorType,
- monitorId
- );
- const normalizedMonitor = normalizeSecrets(monitorWithSecrets);
+ handler: async (routeContext) => {
+ const { monitorId } = routeContext.request.params;
+ return triggerTestNow(monitorId, routeContext);
+ },
+});
- const { [ConfigKey.SCHEDULE]: schedule, [ConfigKey.LOCATIONS]: locations } =
- monitorWithSecrets.attributes;
+export const triggerTestNow = async (
+ monitorId: string,
+ { server, spaceId, syntheticsMonitorClient }: RouteContext
+): Promise => {
+ const encryptedClient = server.encryptedSavedObjects.getClient();
- const { syntheticsService } = syntheticsMonitorClient;
+ const monitorWithSecrets =
+ await encryptedClient.getDecryptedAsInternalUser(
+ syntheticsMonitorType,
+ monitorId
+ );
+ const normalizedMonitor = normalizeSecrets(monitorWithSecrets);
- const testRunId = uuidv4();
+ const { [ConfigKey.SCHEDULE]: schedule, [ConfigKey.LOCATIONS]: locations } =
+ monitorWithSecrets.attributes;
- const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID;
+ const { syntheticsService } = syntheticsMonitorClient;
- const paramsBySpace = await syntheticsService.getSyntheticsParams({ spaceId });
+ const testRunId = uuidv4();
- const errors = await syntheticsService.runOnceConfigs({
- // making it enabled, even if it's disabled in the UI
- monitor: { ...normalizedMonitor.attributes, enabled: true },
- configId: monitorId,
- heartbeatId: (normalizedMonitor.attributes as MonitorFields)[ConfigKey.MONITOR_QUERY_ID],
- testRunId,
- params: paramsBySpace[spaceId],
- });
+ const paramsBySpace = await syntheticsService.getSyntheticsParams({ spaceId });
- if (errors && errors?.length > 0) {
- return {
- errors,
- testRunId,
- schedule,
- locations,
- configId: monitorId,
- monitor: normalizedMonitor.attributes,
- } as TestNowResponse;
- }
+ const errors = await syntheticsService.runOnceConfigs({
+ // making it enabled, even if it's disabled in the UI
+ monitor: { ...normalizedMonitor.attributes, enabled: true },
+ configId: monitorId,
+ heartbeatId: (normalizedMonitor.attributes as MonitorFields)[ConfigKey.MONITOR_QUERY_ID],
+ testRunId,
+ params: paramsBySpace[spaceId],
+ });
+ if (errors && errors?.length > 0) {
return {
+ errors,
testRunId,
schedule,
locations,
configId: monitorId,
monitor: normalizedMonitor.attributes,
- } as TestNowResponse;
- },
-});
+ };
+ }
+
+ return {
+ testRunId,
+ schedule,
+ locations,
+ configId: monitorId,
+ monitor: normalizedMonitor.attributes,
+ };
+};
diff --git a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts
index 6f9a4baf6ff4b..d65c46c8f54dd 100644
--- a/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts
+++ b/x-pack/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts
@@ -358,8 +358,6 @@ export class SyntheticsMonitorClient {
async inspectMonitor(
monitorObj: { monitor: MonitorFields; id: string },
- request: KibanaRequest,
- savedObjectsClient: SavedObjectsClientContract,
allPrivateLocations: PrivateLocation[],
spaceId: string,
hideParams: boolean,
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 9532b0b5ce67e..e5924a78dcbbb 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -15240,7 +15240,6 @@
"xpack.fleet.packagePolicy.policyNotFoundError": "Politique de package possédant l'ID {id} introuvable",
"xpack.fleet.packagePolicyEditor.datastreamIngestPipelinesLabel": "Les pipelines d'ingestion effectuent des transformations courantes sur les données ingérées. Nous vous conseillons de ne modifier que le pipeline d'ingestion personnalisé. Ces pipelines sont partagés par les stratégies d'intégration du même type d'intégration. Toute modification des pipelines d'ingestion affecte donc toutes les politiques d'intégration. {learnMoreLink}",
"xpack.fleet.packagePolicyEditor.datastreamMappings.description": "La mapping est un processus qui consiste à définir la façon dont un document et les champs qu'il contient sont enregistrés et indexés. Si vous ajoutez de nouveaux champs via le pipeline d'ingestion personnalisé, nous vous conseillons d'ajouter un mapping pour ceux du modèle de composant. {learnMoreLink}",
- "xpack.fleet.packagePolicyEditor.stepConfigure.experimentalFeaturesRolloverWarning": "Après avoir modifié ces paramètres, vous devrez substituer le flux de données existant pour que les modifications soient prises en compte. {learnMoreLink}",
"xpack.fleet.packagePolicyInvalidError": "La politique de package n'est pas valide : {errors}",
"xpack.fleet.packagePolicyValidation.invalidArrayErrorMessage": "Format invalide pour {fieldName} : tableau attendu",
"xpack.fleet.packagePolicyValidation.requiredErrorMessage": "{fieldName} est requis",
@@ -16136,14 +16135,6 @@
"xpack.fleet.packagePolicyEditor.datastreamMappings.inspectBtn": "Inspecter les mappings",
"xpack.fleet.packagePolicyEditor.datastreamMappings.learnMoreLink": "En savoir plus",
"xpack.fleet.packagePolicyEditor.datastreamMappings.title": "Mappings",
- "xpack.fleet.packagePolicyEditor.experimentalFeatureRolloverLearnMore": "En savoir plus",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.docValueOnlyNumericLabel": "Valeur de document uniquement (types numériques)",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.docValueOnlyOtherLabel": "Valeur de document uniquement (autres types)",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.syntheticSourceLabel": "Source synthétique",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.tooltip": "L'indexation TSDB est activée par l'intégration.",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.TSDBLabel": "Indexation de base de données temporelle (TSDB)",
- "xpack.fleet.packagePolicyEditor.experimentalSettings.title": "Paramètres d'indexation (version d'évaluation technique)",
- "xpack.fleet.packagePolicyEditor.stepConfigure.experimentalFeaturesDescription": "Choisissez de quelle façon stocker les index de sauvegarde pour ce flux de données. La modification de ces paramètres peut affecter d'autres propriétés.",
"xpack.fleet.packagePolicyEdotpr.datastreamIngestPipelines.learnMoreLink": "En savoir plus",
"xpack.fleet.packagePolicyField.selectPlaceholder": "Sélectionner une option",
"xpack.fleet.packagePolicyField.yamlCodeEditor": "Éditeur de code YAML",
@@ -20912,8 +20903,6 @@
"xpack.lens.pie.wafflelabel": "Gaufre",
"xpack.lens.pie.waffleSuggestionLabel": "Gaufre",
"xpack.lens.pieChart.categoriesInLegendLabel": "Masquer les étiquettes",
- "xpack.lens.pieChart.color": "Couleur",
- "xpack.lens.pieChart.colorPicker.auto": "Auto",
"xpack.lens.pieChart.colorPicker.disabledBecauseGroupBy": "Vous ne pouvez pas appliquer de couleurs personnalisées à des sections individuelles lorsque le calque inclut une ou plusieurs dimensions \"Regrouper par\".",
"xpack.lens.pieChart.colorPicker.disabledBecauseSliceBy": "Vous ne pouvez pas appliquer de couleurs personnalisées à des sections individuelles lorsque le calque inclut une ou plusieurs dimensions \"Section par\".",
"xpack.lens.pieChart.emptySizeRatioLabel": "Taille de la zone intérieure",
@@ -39455,4 +39444,4 @@
"xpack.painlessLab.title": "Painless Lab",
"xpack.painlessLab.walkthroughButtonLabel": "Présentation"
}
-}
+}
\ No newline at end of file
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c1244907c6c8b..ede24d36d52c3 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -15239,7 +15239,6 @@
"xpack.fleet.packagePolicy.policyNotFoundError": "ID {id}のパッケージポリシーが見つかりません",
"xpack.fleet.packagePolicyEditor.datastreamIngestPipelinesLabel": "インジェストパイプラインは、取り込まれたデータに対して共通の変換を実行します。カスタムインジェストパイプラインのみを修正することをお勧めします。これらのパイプラインは、同じ統合タイプの統合ポリシーの間で共有されます。このため、インジェストパイプラインを修正すると、すべての統合ポリシーに影響します。{learnMoreLink}",
"xpack.fleet.packagePolicyEditor.datastreamMappings.description": "マッピングは、ドキュメントとドキュメントに含まれるフィールドが格納され、インデックスが作成される方法を定義するプロセスです。カスタムインジェストパイプラインで新しいフィールドを追加している場合は、コンポーネントテンプレートでそのフィールドのマッピングを追加することをお勧めします。{learnMoreLink}",
- "xpack.fleet.packagePolicyEditor.stepConfigure.experimentalFeaturesRolloverWarning": "これらの設定を変更した後、変更を有効にするために、既存のデータストリームを手動でロールオーバーする必要があります。{learnMoreLink}",
"xpack.fleet.packagePolicyInvalidError": "パッケージポリシーが無効です:{errors}",
"xpack.fleet.packagePolicyValidation.invalidArrayErrorMessage": "{fieldName}の無効な形式:想定された配列",
"xpack.fleet.packagePolicyValidation.requiredErrorMessage": "{fieldName}は必須です",
@@ -16135,14 +16134,6 @@
"xpack.fleet.packagePolicyEditor.datastreamMappings.inspectBtn": "マッピングを検査",
"xpack.fleet.packagePolicyEditor.datastreamMappings.learnMoreLink": "詳細",
"xpack.fleet.packagePolicyEditor.datastreamMappings.title": "マッピング",
- "xpack.fleet.packagePolicyEditor.experimentalFeatureRolloverLearnMore": "詳細",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.docValueOnlyNumericLabel": "ドキュメント値のみ(数値型)",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.docValueOnlyOtherLabel": "ドキュメント値のみ(他の型)",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.syntheticSourceLabel": "Syntheticソース",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.tooltip": "TSDBインデックスは統合で有効です",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.TSDBLabel": "時系列データベース(TSDB)インデックス",
- "xpack.fleet.packagePolicyEditor.experimentalSettings.title": "インデックス設定(テクニカルプレビュー)",
- "xpack.fleet.packagePolicyEditor.stepConfigure.experimentalFeaturesDescription": "このデータストリームの基本インデックスを保存する方法を選択します。これらの設定を変更すると、他のプロパティに影響する可能性があります。",
"xpack.fleet.packagePolicyEdotpr.datastreamIngestPipelines.learnMoreLink": "詳細",
"xpack.fleet.packagePolicyField.selectPlaceholder": "オプションを選択",
"xpack.fleet.packagePolicyField.yamlCodeEditor": "YAMLコードエディター",
@@ -20912,8 +20903,6 @@
"xpack.lens.pie.wafflelabel": "ワッフル",
"xpack.lens.pie.waffleSuggestionLabel": "ワッフル",
"xpack.lens.pieChart.categoriesInLegendLabel": "ラベルを非表示",
- "xpack.lens.pieChart.color": "色",
- "xpack.lens.pieChart.colorPicker.auto": "自動",
"xpack.lens.pieChart.colorPicker.disabledBecauseGroupBy": "レイヤーに1つ以上の「グループ化基準」ディメンションが含まれる場合、個々のスライスにカスタムカラーを適用することができません。",
"xpack.lens.pieChart.colorPicker.disabledBecauseSliceBy": "レイヤーに1つ以上の「スライス基準」ディメンションが含まれる場合、個々のスライスにカスタムカラーを適用することができません。",
"xpack.lens.pieChart.emptySizeRatioLabel": "内側の領域のサイズ",
@@ -39425,4 +39414,4 @@
"xpack.painlessLab.title": "Painless Lab",
"xpack.painlessLab.walkthroughButtonLabel": "実地検証"
}
-}
+}
\ No newline at end of file
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 1b3de70f6c587..2a4f31a69af74 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -15239,7 +15239,6 @@
"xpack.fleet.packagePolicy.policyNotFoundError": "未找到 ID 为 {id} 的软件包策略",
"xpack.fleet.packagePolicyEditor.datastreamIngestPipelinesLabel": "采集管道会对采集的数据执行常见的转换。我们建议只修改定制采集管道。这些管道将在同一集成类型的集成策略间共享。因此,对采集管道的任何修改都会影响所有集成策略。{learnMoreLink}",
"xpack.fleet.packagePolicyEditor.datastreamMappings.description": "映射是指定义如何存储和索引文档及其包含的字段的过程。如果您正通过定制采集管道添加新字段,我们建议在组件模板中为那些字段添加映射。{learnMoreLink}",
- "xpack.fleet.packagePolicyEditor.stepConfigure.experimentalFeaturesRolloverWarning": "更改这些设置后,您需要手动滚动更新现有数据流以使更改生效。{learnMoreLink}",
"xpack.fleet.packagePolicyInvalidError": "软件包策略无效:{errors}",
"xpack.fleet.packagePolicyValidation.invalidArrayErrorMessage": "{fieldName} 格式无效:需要数组",
"xpack.fleet.packagePolicyValidation.requiredErrorMessage": "{fieldName} 必填",
@@ -16135,14 +16134,6 @@
"xpack.fleet.packagePolicyEditor.datastreamMappings.inspectBtn": "检查映射",
"xpack.fleet.packagePolicyEditor.datastreamMappings.learnMoreLink": "了解详情",
"xpack.fleet.packagePolicyEditor.datastreamMappings.title": "映射",
- "xpack.fleet.packagePolicyEditor.experimentalFeatureRolloverLearnMore": "了解详情",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.docValueOnlyNumericLabel": "仅文档值(数值类型)",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.docValueOnlyOtherLabel": "仅文档值(其他类型)",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.syntheticSourceLabel": "组合源",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.tooltip": "TSDB 索引由此集成启用",
- "xpack.fleet.packagePolicyEditor.experimentalFeatures.TSDBLabel": "时间序列数据库 (TSDB) 索引",
- "xpack.fleet.packagePolicyEditor.experimentalSettings.title": "索引设置(技术预览)",
- "xpack.fleet.packagePolicyEditor.stepConfigure.experimentalFeaturesDescription": "选择您希望如何存储此数据流的后备索引。更改这些设置可能会影响到其他属性。",
"xpack.fleet.packagePolicyEdotpr.datastreamIngestPipelines.learnMoreLink": "了解详情",
"xpack.fleet.packagePolicyField.selectPlaceholder": "选择选项",
"xpack.fleet.packagePolicyField.yamlCodeEditor": "YAML 代码编辑器",
@@ -20912,8 +20903,6 @@
"xpack.lens.pie.wafflelabel": "华夫饼图",
"xpack.lens.pie.waffleSuggestionLabel": "华夫饼图",
"xpack.lens.pieChart.categoriesInLegendLabel": "隐藏标签",
- "xpack.lens.pieChart.color": "颜色",
- "xpack.lens.pieChart.colorPicker.auto": "自动",
"xpack.lens.pieChart.colorPicker.disabledBecauseGroupBy": "图层包括一个或多个“分组依据”维度时,无法将定制颜色应用于单个切片。",
"xpack.lens.pieChart.colorPicker.disabledBecauseSliceBy": "图层包括一个或多个“切片依据”维度时,无法将定制颜色应用于单个切片。",
"xpack.lens.pieChart.emptySizeRatioLabel": "内部面积大小",
@@ -39419,4 +39408,4 @@
"xpack.painlessLab.title": "Painless 实验室",
"xpack.painlessLab.walkthroughButtonLabel": "指导"
}
-}
+}
\ No newline at end of file
diff --git a/x-pack/test/api_integration/apis/asset_manager/config.ts b/x-pack/test/api_integration/apis/asset_manager/config.ts
index 0061b85f8c95c..647ebb8a1c5b2 100644
--- a/x-pack/test/api_integration/apis/asset_manager/config.ts
+++ b/x-pack/test/api_integration/apis/asset_manager/config.ts
@@ -5,14 +5,84 @@
* 2.0.
*/
+import { APM_TEST_PASSWORD } from '@kbn/apm-plugin/server/test_helpers/create_apm_users/authentication';
+import {
+ ApmSynthtraceEsClient,
+ ApmSynthtraceKibanaClient,
+ AssetsSynthtraceEsClient,
+ createLogger,
+ InfraSynthtraceEsClient,
+ LogLevel,
+} from '@kbn/apm-synthtrace';
import { FtrConfigProviderContext } from '@kbn/test';
+import url from 'url';
+import { FtrProviderContext as InheritedFtrProviderContext } from '../../ftr_provider_context';
+import { InheritedServices } from './types';
-export default async function ({ readConfigFile }: FtrConfigProviderContext) {
+interface AssetManagerConfig {
+ services: InheritedServices & {
+ assetsSynthtraceEsClient: (
+ context: InheritedFtrProviderContext
+ ) => Promise;
+ infraSynthtraceEsClient: (
+ context: InheritedFtrProviderContext
+ ) => Promise;
+ apmSynthtraceEsClient: (context: InheritedFtrProviderContext) => Promise;
+ };
+}
+
+export default async function createTestConfig({
+ readConfigFile,
+}: FtrConfigProviderContext): Promise {
const baseIntegrationTestsConfig = await readConfigFile(require.resolve('../../config.ts'));
+ const services = baseIntegrationTestsConfig.get('services');
return {
...baseIntegrationTestsConfig.getAll(),
testFiles: [require.resolve('.')],
+ services: {
+ ...services,
+ assetsSynthtraceEsClient: (context: InheritedFtrProviderContext) => {
+ return new AssetsSynthtraceEsClient({
+ client: context.getService('es'),
+ logger: createLogger(LogLevel.info),
+ refreshAfterIndex: true,
+ });
+ },
+ infraSynthtraceEsClient: (context: InheritedFtrProviderContext) => {
+ return new InfraSynthtraceEsClient({
+ client: context.getService('es'),
+ logger: createLogger(LogLevel.info),
+ refreshAfterIndex: true,
+ });
+ },
+ apmSynthtraceEsClient: async (context: InheritedFtrProviderContext) => {
+ const servers = baseIntegrationTestsConfig.get('servers');
+
+ const kibanaServer = servers.kibana as url.UrlObject;
+ const kibanaServerUrl = url.format(kibanaServer);
+ const kibanaServerUrlWithAuth = url
+ .format({
+ ...url.parse(kibanaServerUrl),
+ auth: `elastic:${APM_TEST_PASSWORD}`,
+ })
+ .slice(0, -1);
+
+ const kibanaClient = new ApmSynthtraceKibanaClient({
+ target: kibanaServerUrlWithAuth,
+ logger: createLogger(LogLevel.debug),
+ });
+ const kibanaVersion = await kibanaClient.fetchLatestApmPackageVersion();
+ await kibanaClient.installApmPackage(kibanaVersion);
+
+ return new ApmSynthtraceEsClient({
+ client: context.getService('es'),
+ logger: createLogger(LogLevel.info),
+ version: kibanaVersion,
+ refreshAfterIndex: true,
+ });
+ },
+ },
kbnTestServer: {
...baseIntegrationTestsConfig.get('kbnTestServer'),
serverArgs: [
@@ -22,3 +92,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
},
};
}
+
+export type CreateTestConfig = Awaited>;
+
+export type AssetManagerServices = CreateTestConfig['services'];
diff --git a/x-pack/test/api_integration/apis/asset_manager/types.ts b/x-pack/test/api_integration/apis/asset_manager/types.ts
new file mode 100644
index 0000000000000..2da488241ee47
--- /dev/null
+++ b/x-pack/test/api_integration/apis/asset_manager/types.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { GenericFtrProviderContext } from '@kbn/test';
+import { FtrProviderContext as InheritedFtrProviderContext } from '../../ftr_provider_context';
+import { AssetManagerServices } from './config';
+
+export type InheritedServices = InheritedFtrProviderContext extends GenericFtrProviderContext<
+ infer TServices,
+ {}
+>
+ ? TServices
+ : {};
+
+export type FtrProviderContext = GenericFtrProviderContext;
diff --git a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts
index 401453d31c59b..84aa1046c4d88 100644
--- a/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/alerts/error_count_threshold.spec.ts
@@ -88,8 +88,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
});
- // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/160299
- describe.skip('create alert', () => {
+ describe('create alert', () => {
before(async () => {
actionId = await createIndexConnector({
supertest,
diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts b/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts
index 936b0286fb478..675145ab8673b 100644
--- a/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts
+++ b/x-pack/test/apm_api_integration/tests/diagnostics/index_pattern_settings.ts
@@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { apm, timerange } from '@kbn/apm-synthtrace-client';
-import { getApmIndexTemplateNames } from '@kbn/apm-plugin/server/routes/diagnostics/get_apm_index_template_names';
+import { getApmIndexTemplateNames } from '@kbn/apm-plugin/server/routes/diagnostics/helpers/get_apm_index_template_names';
import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts b/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts
index d31f326882be5..fcde133347ee3 100644
--- a/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/diagnostics/index_templates.spec.ts
@@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { apm, timerange } from '@kbn/apm-synthtrace-client';
-import { getApmIndexTemplateNames } from '@kbn/apm-plugin/server/routes/diagnostics/get_apm_index_template_names';
+import { getApmIndexTemplateNames } from '@kbn/apm-plugin/server/routes/diagnostics/helpers/get_apm_index_template_names';
import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
diff --git a/x-pack/test/cases_api_integration/common/lib/api/configuration.ts b/x-pack/test/cases_api_integration/common/lib/api/configuration.ts
index 17c6e1f6eadbb..2d3f7312af474 100644
--- a/x-pack/test/cases_api_integration/common/lib/api/configuration.ts
+++ b/x-pack/test/cases_api_integration/common/lib/api/configuration.ts
@@ -5,13 +5,10 @@
* 2.0.
*/
-import {
- CaseConnector,
- ConfigurationRequest,
- Configuration,
- ConnectorTypes,
-} from '@kbn/cases-plugin/common/api';
+import { CaseConnector, ConnectorTypes } from '@kbn/cases-plugin/common/api';
import { CASE_CONFIGURE_URL } from '@kbn/cases-plugin/common/constants';
+import { ConfigurationRequest } from '@kbn/cases-plugin/common/types/api';
+import { Configuration } from '@kbn/cases-plugin/common/types/domain';
import type SuperTest from 'supertest';
import { User } from '../authentication/types';
diff --git a/x-pack/test/cases_api_integration/common/lib/api/connectors.ts b/x-pack/test/cases_api_integration/common/lib/api/connectors.ts
index 9c5c9506cffb3..3e5f499c2314b 100644
--- a/x-pack/test/cases_api_integration/common/lib/api/connectors.ts
+++ b/x-pack/test/cases_api_integration/common/lib/api/connectors.ts
@@ -11,7 +11,6 @@ import http from 'http';
import type SuperTest from 'supertest';
import { CASE_CONFIGURE_CONNECTORS_URL } from '@kbn/cases-plugin/common/constants';
import {
- Configuration,
CaseConnector,
ConnectorTypes,
CasePostRequest,
@@ -22,6 +21,7 @@ import {
import { ActionResult, FindActionResult } from '@kbn/actions-plugin/server/types';
import { getServiceNowServer } from '@kbn/actions-simulators-plugin/server/plugin';
import { RecordingServiceNowSimulator } from '@kbn/actions-simulators-plugin/server/servicenow_simulation';
+import { Configuration } from '@kbn/cases-plugin/common/types/domain';
import { User } from '../authentication/types';
import { superUser } from '../authentication/users';
import { getPostCaseRequest } from '../mock';
diff --git a/x-pack/test/cases_api_integration/common/lib/api/index.ts b/x-pack/test/cases_api_integration/common/lib/api/index.ts
index 70489015c1777..1a758f2ddbea8 100644
--- a/x-pack/test/cases_api_integration/common/lib/api/index.ts
+++ b/x-pack/test/cases_api_integration/common/lib/api/index.ts
@@ -27,15 +27,12 @@ import {
INTERNAL_GET_CASE_CATEGORIES_URL,
} from '@kbn/cases-plugin/common/constants';
import {
- Configuration,
Case,
CaseStatuses,
Cases,
CasesFindResponse,
CasesPatchRequest,
- ConfigurationPatchRequest,
CasesStatusResponse,
- Configurations,
AlertResponse,
ConnectorMappingsAttributes,
CasesByAlertId,
@@ -49,6 +46,8 @@ import { ActionResult } from '@kbn/actions-plugin/server/types';
import { CasePersistedAttributes } from '@kbn/cases-plugin/server/common/types/case';
import type { SavedObjectsRawDocSource } from '@kbn/core/server';
import type { ConfigurationPersistedAttributes } from '@kbn/cases-plugin/server/common/types/configure';
+import { Configurations, Configuration } from '@kbn/cases-plugin/common/types/domain';
+import { ConfigurationPatchRequest } from '@kbn/cases-plugin/common/types/api';
import { User } from '../authentication/types';
import { superUser } from '../authentication/users';
import { getSpaceUrlPrefix, setupAuth } from './helpers';
diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts
new file mode 100644
index 0000000000000..8a5419efdfe17
--- /dev/null
+++ b/x-pack/test/detection_engine_api_integration/basic/tests/coverage_overview.ts
@@ -0,0 +1,420 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+
+import { RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL } from '@kbn/security-solution-plugin/common/detection_engine/rule_management/api/urls';
+import { ThreatArray } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema';
+import { FtrProviderContext } from '../../common/ftr_provider_context';
+import {
+ createPrebuiltRuleAssetSavedObjects,
+ createRuleAssetSavedObject,
+ createRule,
+ deleteAllRules,
+ getSimpleRule,
+ installPrebuiltRulesAndTimelines,
+} from '../../utils';
+
+// eslint-disable-next-line import/no-default-export
+export default ({ getService }: FtrProviderContext): void => {
+ const supertest = getService('supertest');
+ const log = getService('log');
+ const es = getService('es');
+
+ describe('coverage_overview', () => {
+ beforeEach(async () => {
+ await deleteAllRules(supertest, log);
+ });
+
+ describe('without filters', () => {
+ it('returns an empty response if there are no rules', async () => {
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({})
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {},
+ unmapped_rule_ids: [],
+ rules_data: {},
+ });
+ });
+
+ it('returns response with a single rule mapped to MITRE categories', async () => {
+ const rule1 = await createRule(supertest, log, {
+ ...getSimpleRule(),
+ threat: generateThreatArray(1),
+ });
+
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({})
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {
+ T001: [rule1.id],
+ TA001: [rule1.id],
+ 'T001.001': [rule1.id],
+ },
+ unmapped_rule_ids: [],
+ rules_data: {
+ [rule1.id]: {
+ activity: 'disabled',
+ name: 'Simple Rule Query',
+ },
+ },
+ });
+ });
+
+ it('returns response with an unmapped rule', async () => {
+ const rule1 = await createRule(supertest, log, { ...getSimpleRule(), threat: undefined });
+
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({})
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {},
+ unmapped_rule_ids: [rule1.id],
+ rules_data: {
+ [rule1.id]: {
+ activity: 'disabled',
+ name: 'Simple Rule Query',
+ },
+ },
+ });
+ });
+ });
+
+ describe('with filters', () => {
+ describe('search_term', () => {
+ it('returns response filtered by tactic', async () => {
+ await createRule(supertest, log, {
+ ...getSimpleRule('rule-1'),
+ threat: generateThreatArray(1),
+ });
+ const expectedRule = await createRule(supertest, log, {
+ ...getSimpleRule('rule-2'),
+ threat: generateThreatArray(2),
+ });
+
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({
+ filter: {
+ search_term: 'TA002',
+ },
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {
+ T002: [expectedRule.id],
+ TA002: [expectedRule.id],
+ 'T002.002': [expectedRule.id],
+ },
+ unmapped_rule_ids: [],
+ rules_data: {
+ [expectedRule.id]: {
+ activity: 'disabled',
+ name: 'Simple Rule Query',
+ },
+ },
+ });
+ });
+
+ it('returns response filtered by technique', async () => {
+ await createRule(supertest, log, {
+ ...getSimpleRule('rule-1'),
+ threat: generateThreatArray(1),
+ });
+ const expectedRule = await createRule(supertest, log, {
+ ...getSimpleRule('rule-2'),
+ threat: generateThreatArray(2),
+ });
+
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({
+ filter: {
+ search_term: 'T002',
+ },
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {
+ T002: [expectedRule.id],
+ TA002: [expectedRule.id],
+ 'T002.002': [expectedRule.id],
+ },
+ unmapped_rule_ids: [],
+ rules_data: {
+ [expectedRule.id]: {
+ activity: 'disabled',
+ name: 'Simple Rule Query',
+ },
+ },
+ });
+ });
+
+ it('returns response filtered by subtechnique', async () => {
+ await createRule(supertest, log, {
+ ...getSimpleRule('rule-1'),
+ threat: generateThreatArray(1),
+ });
+ const expectedRule = await createRule(supertest, log, {
+ ...getSimpleRule('rule-2'),
+ threat: generateThreatArray(2),
+ });
+
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({
+ filter: {
+ search_term: 'T002.002',
+ },
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {
+ T002: [expectedRule.id],
+ TA002: [expectedRule.id],
+ 'T002.002': [expectedRule.id],
+ },
+ unmapped_rule_ids: [],
+ rules_data: {
+ [expectedRule.id]: {
+ activity: 'disabled',
+ name: 'Simple Rule Query',
+ },
+ },
+ });
+ });
+
+ it('returns response filtered by rule name', async () => {
+ await createRule(supertest, log, getSimpleRule('rule-1'));
+ const expectedRule = await createRule(supertest, log, {
+ ...getSimpleRule('rule-2'),
+ name: 'rule-2',
+ });
+
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({
+ filter: {
+ search_term: 'rule-2',
+ },
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {},
+ unmapped_rule_ids: [expectedRule.id],
+ rules_data: {
+ [expectedRule.id]: {
+ activity: 'disabled',
+ name: 'rule-2',
+ },
+ },
+ });
+ });
+
+ it('returns response filtered by index pattern', async () => {
+ await createRule(supertest, log, {
+ ...getSimpleRule('rule-1'),
+ index: ['index-pattern-1'],
+ });
+ const expectedRule = await createRule(supertest, log, {
+ ...getSimpleRule('rule-2'),
+ index: ['index-pattern-2'],
+ });
+
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({
+ filter: {
+ search_term: 'index-pattern-2',
+ },
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {},
+ unmapped_rule_ids: [expectedRule.id],
+ rules_data: {
+ [expectedRule.id]: {
+ activity: 'disabled',
+ name: 'Simple Rule Query',
+ },
+ },
+ });
+ });
+ });
+
+ describe('activity', () => {
+ it('returns response filtered by disabled rules', async () => {
+ const expectedRule = await createRule(supertest, log, {
+ ...getSimpleRule('rule-1'),
+ threat: generateThreatArray(1),
+ });
+ await createRule(supertest, log, {
+ ...getSimpleRule('rule-2', true),
+ threat: generateThreatArray(2),
+ });
+
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({
+ filter: {
+ activity: ['disabled'],
+ },
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {
+ T001: [expectedRule.id],
+ TA001: [expectedRule.id],
+ 'T001.001': [expectedRule.id],
+ },
+ unmapped_rule_ids: [],
+ rules_data: {
+ [expectedRule.id]: {
+ activity: 'disabled',
+ name: 'Simple Rule Query',
+ },
+ },
+ });
+ });
+
+ it('returns response filtered by enabled rules', async () => {
+ await createRule(supertest, log, {
+ ...getSimpleRule('rule-1'),
+ threat: generateThreatArray(1),
+ });
+ const expectedRule = await createRule(supertest, log, {
+ ...getSimpleRule('rule-2', true),
+ threat: generateThreatArray(2),
+ });
+
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({
+ filter: {
+ activity: ['enabled'],
+ },
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {
+ T002: [expectedRule.id],
+ TA002: [expectedRule.id],
+ 'T002.002': [expectedRule.id],
+ },
+ unmapped_rule_ids: [],
+ rules_data: {
+ [expectedRule.id]: {
+ activity: 'enabled',
+ name: 'Simple Rule Query',
+ },
+ },
+ });
+ });
+ });
+
+ describe('source', () => {
+ it('returns response filtered by custom rules', async () => {
+ await createPrebuiltRuleAssetSavedObjects(es, [
+ createRuleAssetSavedObject({
+ rule_id: 'prebuilt-rule-1',
+ threat: generateThreatArray(1),
+ }),
+ ]);
+ await installPrebuiltRulesAndTimelines(supertest);
+
+ const expectedRule = await createRule(supertest, log, {
+ ...getSimpleRule('rule-1'),
+ threat: generateThreatArray(2),
+ });
+
+ const { body } = await supertest
+ .post(RULE_MANAGEMENT_COVERAGE_OVERVIEW_URL)
+ .set('kbn-xsrf', 'true')
+ .send({
+ filter: {
+ source: ['custom'],
+ },
+ })
+ .expect(200);
+
+ expect(body).to.eql({
+ coverage: {
+ T002: [expectedRule.id],
+ TA002: [expectedRule.id],
+ 'T002.002': [expectedRule.id],
+ },
+ unmapped_rule_ids: [],
+ rules_data: {
+ [expectedRule.id]: {
+ activity: 'disabled',
+ name: 'Simple Rule Query',
+ },
+ },
+ });
+ });
+ });
+ });
+ });
+};
+
+function generateThreatArray(startIndex: number, count = 1): ThreatArray {
+ const result: ThreatArray = [];
+
+ for (let i = 0; i < count; ++i) {
+ const indexName = (i + startIndex).toString().padStart(3, '0');
+
+ result.push({
+ framework: 'MITRE ATT&CK',
+ tactic: {
+ id: `TA${indexName}`,
+ name: `Tactic ${indexName}`,
+ reference: `http://some-link-${indexName}`,
+ },
+ technique: [
+ {
+ id: `T${indexName}`,
+ name: `Technique ${indexName}`,
+ reference: `http://some-technique-link-${indexName}`,
+ subtechnique: [
+ {
+ id: `T${indexName}.${indexName}`,
+ name: `Subtechnique ${indexName}`,
+ reference: `http://some-sub-technique-link-${indexName}`,
+ },
+ ],
+ },
+ ],
+ });
+ }
+
+ return result;
+}
diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts
index 06f648cc83819..57f823aceecd6 100644
--- a/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts
+++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_rules.ts
@@ -11,9 +11,7 @@ import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
createRule,
- createSignalsIndex,
deleteAllRules,
- deleteAllAlerts,
getComplexRule,
getComplexRuleOutput,
getSimpleRule,
@@ -25,15 +23,9 @@ import {
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const log = getService('log');
- const es = getService('es');
describe('find_rules', () => {
beforeEach(async () => {
- await createSignalsIndex(supertest, log);
- });
-
- afterEach(async () => {
- await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
});
diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts
index ffcffb706bcec..315a06043684f 100644
--- a/x-pack/test/detection_engine_api_integration/basic/tests/index.ts
+++ b/x-pack/test/detection_engine_api_integration/basic/tests/index.ts
@@ -25,5 +25,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
loadTestFile(require.resolve('./query_signals'));
loadTestFile(require.resolve('./open_close_signals'));
loadTestFile(require.resolve('./import_timelines'));
+ loadTestFile(require.resolve('./coverage_overview'));
});
};
diff --git a/x-pack/test/detection_engine_api_integration/common/config.ts b/x-pack/test/detection_engine_api_integration/common/config.ts
index c4c3c44f1c418..7f3598ee8d5e8 100644
--- a/x-pack/test/detection_engine_api_integration/common/config.ts
+++ b/x-pack/test/detection_engine_api_integration/common/config.ts
@@ -78,6 +78,7 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s
'previewTelemetryUrlEnabled',
'riskScoringPersistence',
'riskScoringRoutesEnabled',
+ 'detectionsCoverageOverview',
])}`,
'--xpack.task_manager.poll_interval=1000',
`--xpack.actions.preconfigured=${JSON.stringify({
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts
index 9359f21f41e17..795aba6f1cf33 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/find_rules.ts
@@ -11,9 +11,7 @@ import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common
import { FtrProviderContext } from '../../common/ftr_provider_context';
import {
createRule,
- createSignalsIndex,
deleteAllRules,
- deleteAllAlerts,
getComplexRule,
getComplexRuleOutput,
getSimpleRule,
@@ -26,15 +24,9 @@ import {
export default ({ getService }: FtrProviderContext): void => {
const supertest = getService('supertest');
const log = getService('log');
- const es = getService('es');
describe('find_rules', () => {
beforeEach(async () => {
- await createSignalsIndex(supertest, log);
- });
-
- afterEach(async () => {
- await deleteAllAlerts(supertest, log, es);
await deleteAllRules(supertest, log);
});
diff --git a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts
index 16d551485b467..2692c40af0adf 100644
--- a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts
+++ b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts
@@ -34,8 +34,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
return colorMapping;
}
- // Failing: See https://github.com/elastic/kibana/issues/148557
- describe.skip('sync colors', function () {
+
+ describe('sync colors', function () {
before(async function () {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.importExport.load(
diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/import.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/import.ts
index 45f8acf18c55e..04631641904a0 100644
--- a/x-pack/test/saved_object_api_integration/spaces_only/apis/import.ts
+++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/import.ts
@@ -147,10 +147,7 @@ export default function ({ getService }: FtrProviderContext) {
return tests.flat();
};
- // FLAKY: https://github.com/elastic/kibana/issues/158586
- // Also, https://github.com/elastic/kibana/issues/158918
- // esArchiver fails with no_shard_available_action_exception after deleting indexes
- describe.skip('_import', () => {
+ describe('_import', () => {
getTestScenarios([
[false, false],
[false, true],
diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve.ts
index e5c995fd9dabc..1f5e1d3cbf3a0 100644
--- a/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve.ts
+++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve.ts
@@ -41,10 +41,7 @@ export default function ({ getService }: FtrProviderContext) {
return createTestDefinitions(testCases, false, { spaceId });
};
- // FLAKY: https://github.com/elastic/kibana/issues/156998, https://github.com/elastic/kibana/issues/156922, https://github.com/elastic/kibana/issues/156921
- // Also, https://github.com/elastic/kibana/issues/158918
- // esArchiver fails with no_shard_available_action_exception after deleting indexes
- describe.skip('_resolve', () => {
+ describe('_resolve', () => {
getTestScenarios().spaces.forEach(({ spaceId }) => {
const tests = createTests(spaceId);
addTests(`within the ${spaceId} space`, { spaceId, tests });
diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve_import_errors.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve_import_errors.ts
index 7fcacb8daca4e..d76658eddd3a7 100644
--- a/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve_import_errors.ts
+++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/resolve_import_errors.ts
@@ -130,10 +130,7 @@ export default function ({ getService }: FtrProviderContext) {
return createTestDefinitions(testCases, false, { overwrite, spaceId, singleRequest });
};
- // FLAKY: https://github.com/elastic/kibana/issues/155846, https://github.com/elastic/kibana/issues/156045, https://github.com/elastic/kibana/issues/156041
- // Also, https://github.com/elastic/kibana/issues/158918
- // esArchiver fails with no_shard_available_action_exception after deleting indexes
- describe.skip('_resolve_import_errors', () => {
+ describe('_resolve_import_errors', () => {
getTestScenarios([
[false, false],
[false, true],
diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/update.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/update.ts
index 61310502fcff9..951499f0c944a 100644
--- a/x-pack/test/saved_object_api_integration/spaces_only/apis/update.ts
+++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/update.ts
@@ -52,8 +52,7 @@ export default function ({ getService }: FtrProviderContext) {
return createTestDefinitions(testCases, false);
};
- // FLAKY: https://github.com/elastic/kibana/issues/157452
- describe.skip('_update', () => {
+ describe('_update', () => {
getTestScenarios().spaces.forEach(({ spaceId }) => {
const tests = createTests(spaceId);
addTests(`within the ${spaceId} space`, { spaceId, tests });