Skip to content

Commit

Permalink
[Cases] Adding _find API for user actions (#148861)
Browse files Browse the repository at this point in the history
This PR adds a new find API for retrieving a subset of the user actions
for a case.

Issue: #134344

```
GET /api/cases/<case_id>/user_actions/_find
Query Paramaters
{
  types?: Array of "assignees" | "comment" | "connector" | "description" | "pushed" | "tags" | "title" | "status" | "settings" | "severity" | "create_case" | "delete_case" | "action" | "alert" | "user" | "attachment"
  sortOrder?: "asc" | "desc"
  page?: number as a string
  perPage?: number as a string
}
```

<details><summary>Example request and response</summary>

Request
```
curl --location --request GET 'http://localhost:5601/api/cases/8df5fe00-96b1-11ed-9341-471c9630b5ec/user_actions/_find?types=create_case&sortOrder=asc' \
--header 'kbn-xsrf: hello' \
--header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' \
--data-raw ''
```


Response
```
{
    "userActions": [
        {
            "created_at": "2023-01-17T21:54:45.527Z",
            "created_by": {
                "username": "elastic",
                "full_name": null,
                "email": null,
                "profile_uid": "u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0"
            },
            "owner": "cases",
            "action": "create",
            "payload": {
                "title": "Awesome case",
                "tags": [],
                "severity": "low",
                "description": "super",
                "assignees": [],
                "connector": {
                    "name": "none",
                    "type": ".none",
                    "fields": null,
                    "id": "none"
                },
                "settings": {
                    "syncAlerts": false
                },
                "owner": "cases",
                "status": "open"
            },
            "type": "create_case",
            "id": "8e121180-96b1-11ed-9341-471c9630b5ec",
            "case_id": "8df5fe00-96b1-11ed-9341-471c9630b5ec",
            "comment_id": null
        }
    ],
    "page": 1,
    "perPage": 20,
    "total": 1
}
```

</details>

## Notable Changes
- Created the new `_find` route
- Created a new `UserActionFinder` class and moved the find* methods
from the `index.ts` file into there as well as the new find logic
- Extracted the transform logic to its own file since its shared between
multiple files now
- Extracted the user action related integration test functions to the
`user_action.ts` utility file

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: lcawl <[email protected]>
  • Loading branch information
3 people authored Jan 23, 2023
1 parent a31328c commit a78fece
Show file tree
Hide file tree
Showing 66 changed files with 3,948 additions and 1,021 deletions.
127 changes: 126 additions & 1 deletion docs/api-generated/cases/case-apis-passthru.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Any modifications made to this file will be overwritten.
<li><a href="#deleteCase"><code><span class="http-method">delete</span> /s/{spaceId}/api/cases</code></a></li>
<li><a href="#deleteCaseComment"><code><span class="http-method">delete</span> /s/{spaceId}/api/cases/{caseId}/comments/{commentId}</code></a></li>
<li><a href="#deleteCaseComments"><code><span class="http-method">delete</span> /s/{spaceId}/api/cases/{caseId}/comments</code></a></li>
<li><a href="#findCaseActivity"><code><span class="http-method">get</span> /s/{spaceId}/api/cases/{caseId}/user_actions/_find</code></a></li>
<li><a href="#getAllCaseComments"><code><span class="http-method">get</span> /s/{spaceId}/api/cases/{caseId}/comments</code></a></li>
<li><a href="#getCase"><code><span class="http-method">get</span> /s/{spaceId}/api/cases/{caseId}</code></a></li>
<li><a href="#getCaseActivity"><code><span class="http-method">get</span> /s/{spaceId}/api/cases/{caseId}/user_actions</code></a></li>
Expand Down Expand Up @@ -435,6 +436,102 @@ Any modifications made to this file will be overwritten.
<a href="#4xx_response">4xx_response</a>
</div> <!-- method -->
<hr/>
<div class="method"><a name="findCaseActivity"/>
<div class="method-path">
<a class="up" href="#__Methods">Up</a>
<pre class="get"><code class="huge"><span class="http-method">get</span> /s/{spaceId}/api/cases/{caseId}/user_actions/_find</code></pre></div>
<div class="method-summary">Finds user activity for a case. (<span class="nickname">findCaseActivity</span>)</div>
<div class="method-notes">You must have <code>read</code> privileges for the <strong>Cases</strong> feature in the <strong>Management</strong>, <strong>Observability</strong>, or <strong>Security</strong> section of the Kibana feature privileges, depending on the owner of the case you're seeking.</div>
<h3 class="field-label">Path parameters</h3>
<div class="field-items">
<div class="param">caseId (required)</div>
<div class="param-desc"><span class="param-type">Path Parameter</span> &mdash; The identifier for the case. To retrieve case IDs, use the find cases API. All non-ASCII characters must be URL encoded. default: null </div><div class="param">spaceId (required)</div>
<div class="param-desc"><span class="param-type">Path Parameter</span> &mdash; An identifier for the space. If <code>/s/</code> and the identifier are omitted from the path, the default space is used. default: null </div>
</div> <!-- field-items -->
<h3 class="field-label">Query parameters</h3>
<div class="field-items">
<div class="param">page (optional)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; The page number to return. default: 1 </div><div class="param">perPage (optional)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; The number of user actions to return per page. default: 20 </div><div class="param">sortOrder (optional)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; Determines the sort order. default: asc </div><div class="param">types (optional)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; Determines the types of user actions to return. default: null </div>
</div> <!-- field-items -->
<h3 class="field-label">Return type</h3>
<div class="return-type">
<a href="#findCaseActivity_200_response">findCaseActivity_200_response</a>
</div>
<!--Todo: process Response Object and its headers, schema, examples -->
<h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: application/json</div>
<pre class="example"><code>{
"userActions" : [ {
"owner" : "cases",
"case_id" : "22df07d0-03b1-11ed-920c-974bfa104448",
"action" : "create",
"created_at" : "2022-05-13T09:16:17.416Z",
"id" : "22fd3e30-03b1-11ed-920c-974bfa104448",
"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"
},
"version" : "WzM1ODg4LDFd"
}, {
"owner" : "cases",
"case_id" : "22df07d0-03b1-11ed-920c-974bfa104448",
"action" : "create",
"created_at" : "2022-05-13T09:16:17.416Z",
"id" : "22fd3e30-03b1-11ed-920c-974bfa104448",
"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"
},
"version" : "WzM1ODg4LDFd"
} ],
"total" : 1,
"perPage" : 6,
"page" : 0
}</code></pre>
<h3 class="field-label">Produces</h3>
This API call produces the following media types according to the <span class="header">Accept</span> request header;
the media type will be conveyed by the <span class="header">Content-Type</span> response header.
<ul>
<li><code>application/json</code></li>
</ul>
<h3 class="field-label">Responses</h3>
<h4 class="field-label">200</h4>
Indicates a successful call.
<a href="#findCaseActivity_200_response">findCaseActivity_200_response</a>
<h4 class="field-label">401</h4>
Authorization information is missing or invalid.
<a href="#4xx_response">4xx_response</a>
</div> <!-- method -->
<hr/>
<div class="method"><a name="getAllCaseComments"/>
<div class="method-path">
<a class="up" href="#__Methods">Up</a>
Expand Down Expand Up @@ -1162,7 +1259,7 @@ Any modifications made to this file will be overwritten.
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; The page number to return. default: 1 </div><div class="param">perPage (optional)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; The number of rules to return per page. default: 20 </div><div class="param">reporters (optional)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; The number of cases to return per page. default: 20 </div><div class="param">reporters (optional)</div>
<div class="param-desc"><span class="param-type">Query Parameter</span> &mdash; Filters the returned cases by the user name of the reporter. default: null </div><div class="param">search (optional)</div>
Expand Down Expand Up @@ -1998,6 +2095,7 @@ Any modifications made to this file will be overwritten.
<li><a href="#create_case_request"><code>create_case_request</code> - Create case request</a></li>
<li><a href="#create_case_request_connector"><code>create_case_request_connector</code> - </a></li>
<li><a href="#external_service"><code>external_service</code> - </a></li>
<li><a href="#findCaseActivity_200_response"><code>findCaseActivity_200_response</code> - </a></li>
<li><a href="#getCaseComment_200_response"><code>getCaseComment_200_response</code> - </a></li>
<li><a href="#getCaseConfiguration_200_response_inner"><code>getCaseConfiguration_200_response_inner</code> - </a></li>
<li><a href="#getCaseConfiguration_200_response_inner_connector"><code>getCaseConfiguration_200_response_inner_connector</code> - </a></li>
Expand Down Expand Up @@ -2043,6 +2141,7 @@ Any modifications made to this file will be overwritten.
<li><a href="#update_case_request"><code>update_case_request</code> - Update case request</a></li>
<li><a href="#update_case_request_cases_inner"><code>update_case_request_cases_inner</code> - </a></li>
<li><a href="#update_user_comment_request_properties"><code>update_user_comment_request_properties</code> - Update case comment request properties for user comments</a></li>
<li><a href="#user_actions_find_response_properties"><code>user_actions_find_response_properties</code> - </a></li>
<li><a href="#user_actions_response_properties"><code>user_actions_response_properties</code> - </a></li>
<li><a href="#user_actions_response_properties_created_by"><code>user_actions_response_properties_created_by</code> - </a></li>
<li><a href="#user_actions_response_properties_payload"><code>user_actions_response_properties_payload</code> - </a></li>
Expand Down Expand Up @@ -2450,6 +2549,16 @@ Any modifications made to this file will be overwritten.
<div class="param">pushed_by (optional)</div><div class="param-desc"><span class="param-type"><a href="#getCaseConfiguration_200_response_inner_updated_by">getCaseConfiguration_200_response_inner_updated_by</a></span> </div>
</div> <!-- field-items -->
</div>
<div class="model">
<h3><a name="findCaseActivity_200_response"><code>findCaseActivity_200_response</code> - </a> <a class="up" href="#__Models">Up</a></h3>
<div class='model-description'></div>
<div class="field-items">
<div class="param">page (optional)</div><div class="param-desc"><span class="param-type"><a href="#integer">Integer</a></span> </div>
<div class="param">perPage (optional)</div><div class="param-desc"><span class="param-type"><a href="#integer">Integer</a></span> </div>
<div class="param">total (optional)</div><div class="param-desc"><span class="param-type"><a href="#integer">Integer</a></span> </div>
<div class="param">userActions (optional)</div><div class="param-desc"><span class="param-type"><a href="#user_actions_find_response_properties">array[user_actions_find_response_properties]</a></span> </div>
</div> <!-- field-items -->
</div>
<div class="model">
<h3><a name="getCaseComment_200_response"><code>getCaseComment_200_response</code> - </a> <a class="up" href="#__Models">Up</a></h3>
<div class='model-description'></div>
Expand Down Expand Up @@ -2887,6 +2996,22 @@ Any modifications made to this file will be overwritten.
<div class="param">version </div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> The current comment version. To retrieve version values, use the get comments API. </div>
</div> <!-- field-items -->
</div>
<div class="model">
<h3><a name="user_actions_find_response_properties"><code>user_actions_find_response_properties</code> - </a> <a class="up" href="#__Models">Up</a></h3>
<div class='model-description'></div>
<div class="field-items">
<div class="param">action </div><div class="param-desc"><span class="param-type"><a href="#actions">actions</a></span> </div>
<div class="param">case_id </div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> </div>
<div class="param">comment_id </div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> </div>
<div class="param">created_at </div><div class="param-desc"><span class="param-type"><a href="#DateTime">Date</a></span> format: date-time</div>
<div class="param">created_by </div><div class="param-desc"><span class="param-type"><a href="#user_actions_response_properties_created_by">user_actions_response_properties_created_by</a></span> </div>
<div class="param">id </div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> </div>
<div class="param">owner </div><div class="param-desc"><span class="param-type"><a href="#owners">owners</a></span> </div>
<div class="param">payload </div><div class="param-desc"><span class="param-type"><a href="#user_actions_response_properties_payload">user_actions_response_properties_payload</a></span> </div>
<div class="param">version </div><div class="param-desc"><span class="param-type"><a href="#string">String</a></span> </div>
<div class="param">type </div><div class="param-desc"><span class="param-type"><a href="#action_types">action_types</a></span> </div>
</div> <!-- field-items -->
</div>
<div class="model">
<h3><a name="user_actions_response_properties"><code>user_actions_response_properties</code> - </a> <a class="up" href="#__Models">Up</a></h3>
<div class='model-description'></div>
Expand Down
9 changes: 9 additions & 0 deletions docs/user/security/audit-logging.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -400,10 +400,19 @@ Refer to the corresponding {es} logs for potential write errors.
| `success` | User has accessed the user activity of a case.
| `failure` | User is not authorized to access the user activity of a case.


.2+| `case_user_actions_find`
| `success` | User has accessed the user activity of a case as part of a search operation.
| `failure` | User is not authorized to access the user activity of a case.

.2+| `case_user_action_get_metrics`
| `success` | User has accessed metrics for the user activity of a case.
| `failure` | User is not authorized to access metrics for the user activity of a case.

.2+| `case_connectors_get`
| `success` | User has accessed the connectors of a case.
| `failure` | User is not authorized to access the connectors of a case.

3+a|
===== Category: web

Expand Down
21 changes: 21 additions & 0 deletions x-pack/plugins/cases/common/api/cases/user_actions/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import * as rt from 'io-ts';
import { UserRT } from '../../user';

/**
* These values are used in a number of places including to define the accepted values in the
* user_actions/_find api. These values should not be removed only new values can be added.
*/
export const ActionTypes = {
assignees: 'assignees',
comment: 'comment',
Expand Down Expand Up @@ -43,6 +47,10 @@ export const UserActionCommonAttributesRt = rt.type({
action: ActionsRt,
});

/**
* This should only be used for the getAll route and it should be removed when the route is removed
* @deprecated use CaseUserActionInjectedIdsRt instead
*/
export const CaseUserActionSavedObjectIdsRt = rt.type({
action_id: rt.string,
case_id: rt.string,
Expand All @@ -51,3 +59,16 @@ export const CaseUserActionSavedObjectIdsRt = rt.type({

export type UserActionWithAttributes<T> = T & rt.TypeOf<typeof UserActionCommonAttributesRt>;
export type UserActionWithResponse<T> = T & rt.TypeOf<typeof CaseUserActionSavedObjectIdsRt>;

/**
* This should be used for all user action types going forward it will be renamed to CaseUserActionSavedObjectIdsRt
* Once the UI is switched to using the new user actions _find API
*/
export const CaseUserActionInjectedIdsRt = rt.type({
comment_id: rt.union([rt.string, rt.null]),
});

/**
* Temporary type until CaseUserActionInjectedIdsRt replaces CaseUserActionSavedObjectIdsRt
*/
export type UserActionWithResponseInjection<T> = T & rt.TypeOf<typeof CaseUserActionInjectedIdsRt>;
75 changes: 2 additions & 73 deletions x-pack/plugins/cases/common/api/cases/user_actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,6 @@
* 2.0.
*/

import * as rt from 'io-ts';

import type { ActionsRt, ActionTypeValues } from './common';
import { UserActionCommonAttributesRt, CaseUserActionSavedObjectIdsRt } from './common';
import { CreateCaseUserActionRt } from './create_case';
import { DescriptionUserActionRt } from './description';
import { CommentUserActionRt } from './comment';
import { ConnectorUserActionRt } from './connector';
import { PushedUserActionRt } from './pushed';
import { TagsUserActionRt } from './tags';
import { TitleUserActionRt } from './title';
import { SettingsUserActionRt } from './settings';
import { StatusUserActionRt } from './status';
import { DeleteCaseUserActionRt } from './delete_case';
import { SeverityUserActionRt } from './severity';
import { AssigneesUserActionRt } from './assignees';

export * from './common';
export * from './comment';
export * from './connector';
Expand All @@ -34,59 +17,5 @@ export * from './status';
export * from './tags';
export * from './title';
export * from './assignees';

const CommonUserActionsRt = rt.union([
DescriptionUserActionRt,
CommentUserActionRt,
TagsUserActionRt,
TitleUserActionRt,
SettingsUserActionRt,
StatusUserActionRt,
SeverityUserActionRt,
AssigneesUserActionRt,
]);

export const UserActionsRt = rt.union([
CommonUserActionsRt,
CreateCaseUserActionRt,
ConnectorUserActionRt,
PushedUserActionRt,
DeleteCaseUserActionRt,
]);

export const UserActionsWithoutConnectorIdRt = rt.union([
CommonUserActionsRt,
CreateCaseUserActionRt,
ConnectorUserActionRt,
PushedUserActionRt,
DeleteCaseUserActionRt,
]);

const CaseUserActionBasicRt = rt.intersection([UserActionsRt, UserActionCommonAttributesRt]);
const CaseUserActionBasicWithoutConnectorIdRt = rt.intersection([
UserActionsWithoutConnectorIdRt,
UserActionCommonAttributesRt,
]);

const CaseUserActionResponseRt = rt.intersection([
CaseUserActionBasicRt,
CaseUserActionSavedObjectIdsRt,
]);

export const CaseUserActionAttributesRt = CaseUserActionBasicRt;
export const CaseUserActionsResponseRt = rt.array(CaseUserActionResponseRt);

export type CaseUserActionAttributes = rt.TypeOf<typeof CaseUserActionAttributesRt>;
export type CaseUserActionAttributesWithoutConnectorId = rt.TypeOf<
typeof CaseUserActionAttributesRt
>;
export type CaseUserActionsResponse = rt.TypeOf<typeof CaseUserActionsResponseRt>;
export type CaseUserActionResponse = rt.TypeOf<typeof CaseUserActionResponseRt>;

export type UserAction = rt.TypeOf<typeof ActionsRt>;
export type UserActionTypes = ActionTypeValues;

export type CaseUserAction = rt.TypeOf<typeof CaseUserActionBasicRt>;
export type CaseUserActionWithoutConnectorId = rt.TypeOf<
typeof CaseUserActionBasicWithoutConnectorIdRt
>;
export * from './operations';
export * from './response';
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 { CaseUserActionsResponseWithoutActionIdRt } from '../response';
import { ActionTypes } from '../common';
import { NumberFromString } from '../../../saved_object';

const AdditionalFilterTypes = {
action: 'action',
alert: 'alert',
user: 'user',
attachment: 'attachment',
} as const;

export const FindTypes = {
...ActionTypes,
...AdditionalFilterTypes,
} as const;

const FindTypeFieldRt = rt.keyof(FindTypes);

export type FindTypeField = rt.TypeOf<typeof FindTypeFieldRt>;

export const UserActionFindRequestRt = rt.partial({
types: rt.array(FindTypeFieldRt),
sortOrder: rt.union([rt.literal('desc'), rt.literal('asc')]),
page: NumberFromString,
perPage: NumberFromString,
});

export type UserActionFindRequest = rt.TypeOf<typeof UserActionFindRequestRt>;

export const UserActionFindResponseRt = rt.type({
userActions: CaseUserActionsResponseWithoutActionIdRt,
page: rt.number,
perPage: rt.number,
total: rt.number,
});

export type UserActionFindResponse = rt.TypeOf<typeof UserActionFindResponseRt>;
Original file line number Diff line number Diff line change
@@ -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 './find';
Loading

0 comments on commit a78fece

Please sign in to comment.