From a0294e59f1c712cd0e46c56e4aa667c9bc4b4f9d Mon Sep 17 00:00:00 2001
From: Esteban Beltran <academo@users.noreply.github.com>
Date: Thu, 5 May 2022 10:53:03 +0200
Subject: [PATCH] [Cases] Add the severity field to the cases API (#131394)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Add severity field to create API and migration

* Adds integration test for severity field migration

* remove exclusive test

* Change severity levels

* Update integration tests for post case

* Add more integration tests

* Fix all cases list test

* Fix some server test

* Fix util server test

* Fix client util test

* Convert event log's duration from number to string in Kibana (keep as "long" in Elasticsearch) (#130819)

* Convert event.duration to string in TypeScript, keep as long in Elasticsearch

* Fix jest test

* Fix functional tests

* Add ecsStringOrNumber to event log schema

* Fix jest test

* Add utility functions to event log plugin

* Use new event log utility functions

* PR fixes

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

* filter o11y rule aggregations (#131301)

* [Cloud Posture] Display and save rules per benchmark (#131412)

* Adding aria-label for discover data grid select document checkbox (#131277)

* Update API docs (#130999)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

* [CI] Use GCS buckets for bazel remote caching (#131345)

* [Actionable Observability] Add license modal to rules table (#131232)

* Add fix license link

* fix localization

* fix CI error

* fix more translation issues

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

* [RAM] Add shareable rule status filter (#130705)

* rule state filter

* turn off experiment

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Status filter API call

* Fix tests

* rename state to status, added tests

* Address comments and fix tests

* Revert experiment flag

* Remove unused translations

* Addressed comments

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>

* [storybook] Watch for changes in packages (#131467)

* [storybook] Watch for changes in packages

* Update default_config.ts

* Improve saved objects migrations failure errors and logs (#131359)

* [Unified observability] Add tour step to guided setup (#131149)

* [Lens] Improved interval input (#131372)

* [Vega] Adjust vega doc for usage of ems files (#130948)

* adjust vega doc

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Nick Peihl <nickpeihl@gmail.com>

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Nick Peihl <nickpeihl@gmail.com>

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Nick Peihl <nickpeihl@gmail.com>

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Nick Peihl <nickpeihl@gmail.com>

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Nick Peihl <nickpeihl@gmail.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Nick Peihl <nickpeihl@gmail.com>

* Excess intersections

* Create severity user action

* Add severity to create_case user action

* Fix and add integration tests

* Minor improvements

Co-authored-by: Mike Côté <mikecote@users.noreply.github.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: mgiota <panagiota.mitsopoulou@elastic.co>
Co-authored-by: Jordan <51442161+JordanSh@users.noreply.github.com>
Co-authored-by: Bhavya RM <bhavya@elastic.co>
Co-authored-by: Thomas Neirynck <thomas@elastic.co>
Co-authored-by: Brian Seeders <brian.seeders@elastic.co>
Co-authored-by: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com>
Co-authored-by: Clint Andrew Hall <clint.hall@elastic.co>
Co-authored-by: Christiane (Tina) Heiligers <christiane.heiligers@elastic.co>
Co-authored-by: Alejandro Fernández Gómez <alejandro.fernandez@elastic.co>
Co-authored-by: Joe Reuter <johannes.reuter@elastic.co>
Co-authored-by: Nick Peihl <nickpeihl@gmail.com>
Co-authored-by: Christos Nasikas <christos.nasikas@elastic.co>
---
 x-pack/plugins/cases/common/api/cases/case.ts |  81 +++++++----
 .../common/api/cases/user_actions/common.ts   |   1 +
 .../api/cases/user_actions/create_case.ts     |   1 +
 .../common/api/cases/user_actions/index.ts    |   2 +
 .../common/api/cases/user_actions/severity.ts |  19 +++
 .../plugins/cases/common/api/runtime_types.ts |  94 +++++++-----
 .../all_cases/all_cases_list.test.tsx         |   3 +-
 .../components/user_actions/builder.tsx       |   4 +
 .../plugins/cases/public/containers/mock.ts   |   4 +
 .../cases/server/client/cases/create.ts       |   5 +-
 .../plugins/cases/server/client/cases/mock.ts |   1 +
 .../plugins/cases/server/client/utils.test.ts |  71 ---------
 .../plugins/cases/server/common/utils.test.ts | 135 ++++++++++++++++++
 x-pack/plugins/cases/server/common/utils.ts   |   2 +
 .../api/__fixtures__/mock_saved_objects.ts    |   5 +
 .../cases/server/saved_object_types/cases.ts  |   3 +
 .../migrations/cases.test.ts                  |  44 +++++-
 .../saved_object_types/migrations/cases.ts    |  12 +-
 .../saved_object_types/migrations/utils.ts    |   9 +-
 .../cases/server/services/cases/index.test.ts |   2 +
 .../cases/server/services/test_utils.ts       |   8 +-
 .../user_actions/builder_factory.test.ts      |  36 +++++
 .../services/user_actions/builder_factory.ts  |   2 +
 .../user_actions/builders/severity.ts         |  22 +++
 .../services/user_actions/index.test.ts       |  55 +++++++
 .../server/services/user_actions/mocks.ts     |   4 +-
 .../server/services/user_actions/types.ts     |   4 +
 .../cases_api_integration/common/lib/mock.ts  |   2 +
 .../tests/common/cases/import_export.ts       |   6 +
 .../tests/common/cases/migrations.ts          |  56 +++++---
 .../tests/common/cases/patch_cases.ts         |  45 ++++++
 .../tests/common/cases/post_case.ts           |  33 +++++
 .../user_actions/get_all_user_actions.ts      |  25 ++++
 33 files changed, 629 insertions(+), 167 deletions(-)
 create mode 100644 x-pack/plugins/cases/common/api/cases/user_actions/severity.ts
 create mode 100644 x-pack/plugins/cases/server/services/user_actions/builders/severity.ts

diff --git a/x-pack/plugins/cases/common/api/cases/case.ts b/x-pack/plugins/cases/common/api/cases/case.ts
index 3f42e5b5c875c..b3dbe4801f544 100644
--- a/x-pack/plugins/cases/common/api/cases/case.ts
+++ b/x-pack/plugins/cases/common/api/cases/case.ts
@@ -39,6 +39,20 @@ export const SettingsRt = rt.type({
   syncAlerts: rt.boolean,
 });
 
+export enum CaseSeverity {
+  LOW = 'low',
+  MEDIUM = 'medium',
+  HIGH = 'high',
+  CRITICAL = 'critical',
+}
+
+export const CaseSeverityRt = rt.union([
+  rt.literal(CaseSeverity.LOW),
+  rt.literal(CaseSeverity.MEDIUM),
+  rt.literal(CaseSeverity.HIGH),
+  rt.literal(CaseSeverity.CRITICAL),
+]);
+
 const CaseBasicRt = rt.type({
   /**
    * The description of the case
@@ -68,6 +82,10 @@ const CaseBasicRt = rt.type({
    * The plugin owner of the case
    */
   owner: rt.string,
+  /**
+   * The severity of the case
+   */
+  severity: CaseSeverityRt,
 });
 
 /**
@@ -106,33 +124,42 @@ export const CaseAttributesRt = rt.intersection([
   }),
 ]);
 
-export const CasePostRequestRt = rt.type({
-  /**
-   * Description of the case
-   */
-  description: rt.string,
-  /**
-   * Identifiers for the case.
-   */
-  tags: rt.array(rt.string),
-  /**
-   * Title of the case
-   */
-  title: rt.string,
-  /**
-   * The external configuration for the case
-   */
-  connector: CaseConnectorRt,
-  /**
-   * Sync settings for alerts
-   */
-  settings: SettingsRt,
-  /**
-   * The owner here must match the string used when a plugin registers a feature with access to the cases plugin. The user
-   * creating this case must also be granted access to that plugin's feature.
-   */
-  owner: rt.string,
-});
+export const CasePostRequestRt = rt.intersection([
+  rt.type({
+    /**
+     * Description of the case
+     */
+    description: rt.string,
+    /**
+     * Identifiers for the case.
+     */
+    tags: rt.array(rt.string),
+    /**
+     * Title of the case
+     */
+    title: rt.string,
+    /**
+     * The external configuration for the case
+     */
+    connector: CaseConnectorRt,
+    /**
+     * Sync settings for alerts
+     */
+    settings: SettingsRt,
+    /**
+     * The owner here must match the string used when a plugin registers a feature with access to the cases plugin. The user
+     * creating this case must also be granted access to that plugin's feature.
+     */
+    owner: rt.string,
+  }),
+  rt.partial({
+    /**
+     * The severity of the case. The severity is
+     * default it to "low" if not provided.
+     */
+    severity: CaseSeverityRt,
+  }),
+]);
 
 export const CasesFindRequestRt = rt.partial({
   /**
diff --git a/x-pack/plugins/cases/common/api/cases/user_actions/common.ts b/x-pack/plugins/cases/common/api/cases/user_actions/common.ts
index a6d12d135c142..5665ab524071a 100644
--- a/x-pack/plugins/cases/common/api/cases/user_actions/common.ts
+++ b/x-pack/plugins/cases/common/api/cases/user_actions/common.ts
@@ -17,6 +17,7 @@ export const ActionTypes = {
   title: 'title',
   status: 'status',
   settings: 'settings',
+  severity: 'severity',
   create_case: 'create_case',
   delete_case: 'delete_case',
 } as const;
diff --git a/x-pack/plugins/cases/common/api/cases/user_actions/create_case.ts b/x-pack/plugins/cases/common/api/cases/user_actions/create_case.ts
index c491cc519132f..53d2320b5afd4 100644
--- a/x-pack/plugins/cases/common/api/cases/user_actions/create_case.ts
+++ b/x-pack/plugins/cases/common/api/cases/user_actions/create_case.ts
@@ -23,6 +23,7 @@ export const CommonFieldsRt = rt.type({
 const CommonPayloadAttributesRt = rt.type({
   description: DescriptionUserActionPayloadRt.props.description,
   status: rt.string,
+  severity: rt.string,
   tags: TagsUserActionPayloadRt.props.tags,
   title: TitleUserActionPayloadRt.props.title,
   settings: SettingsUserActionPayloadRt.props.settings,
diff --git a/x-pack/plugins/cases/common/api/cases/user_actions/index.ts b/x-pack/plugins/cases/common/api/cases/user_actions/index.ts
index 3f974d89fc79a..d19ee5fcbe9f8 100644
--- a/x-pack/plugins/cases/common/api/cases/user_actions/index.ts
+++ b/x-pack/plugins/cases/common/api/cases/user_actions/index.ts
@@ -23,6 +23,7 @@ import { TitleUserActionRt } from './title';
 import { SettingsUserActionRt } from './settings';
 import { StatusUserActionRt } from './status';
 import { DeleteCaseUserActionRt } from './delete_case';
+import { SeverityUserActionRt } from './severity';
 
 export * from './common';
 export * from './comment';
@@ -43,6 +44,7 @@ const CommonUserActionsRt = rt.union([
   TitleUserActionRt,
   SettingsUserActionRt,
   StatusUserActionRt,
+  SeverityUserActionRt,
 ]);
 
 export const UserActionsRt = rt.union([
diff --git a/x-pack/plugins/cases/common/api/cases/user_actions/severity.ts b/x-pack/plugins/cases/common/api/cases/user_actions/severity.ts
new file mode 100644
index 0000000000000..2db5a0880dc09
--- /dev/null
+++ b/x-pack/plugins/cases/common/api/cases/user_actions/severity.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 * as rt from 'io-ts';
+import { CaseSeverityRt } from '../case';
+import { ActionTypes, UserActionWithAttributes } from './common';
+
+export const SeverityUserActionPayloadRt = rt.type({ severity: CaseSeverityRt });
+
+export const SeverityUserActionRt = rt.type({
+  type: rt.literal(ActionTypes.severity),
+  payload: SeverityUserActionPayloadRt,
+});
+
+export type SeverityUserAction = UserActionWithAttributes<rt.TypeOf<typeof SeverityUserActionRt>>;
diff --git a/x-pack/plugins/cases/common/api/runtime_types.ts b/x-pack/plugins/cases/common/api/runtime_types.ts
index c807d4b31b751..0a31479b29da8 100644
--- a/x-pack/plugins/cases/common/api/runtime_types.ts
+++ b/x-pack/plugins/cases/common/api/runtime_types.ts
@@ -9,42 +9,18 @@ import { either, fold } from 'fp-ts/lib/Either';
 import { identity } from 'fp-ts/lib/function';
 import { pipe } from 'fp-ts/lib/pipeable';
 import * as rt from 'io-ts';
-import { isObject } from 'lodash/fp';
+import { formatErrors } from '@kbn/securitysolution-io-ts-utils';
 
 type ErrorFactory = (message: string) => Error;
-
-/**
- * @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/format_errors/index.ts
- * Bug fix for the TODO is in the format_errors package
- */
-export const formatErrors = (errors: rt.Errors): string[] => {
-  const err = errors.map((error) => {
-    if (error.message != null) {
-      return error.message;
-    } else {
-      const keyContext = error.context
-        .filter(
-          (entry) => entry.key != null && !Number.isInteger(+entry.key) && entry.key.trim() !== ''
-        )
-        .map((entry) => entry.key)
-        .join(',');
-
-      const nameContext = error.context.find((entry) => {
-        // TODO: Put in fix for optional chaining https://github.com/cypress-io/cypress/issues/9298
-        if (entry.type && entry.type.name) {
-          return entry.type.name.length > 0;
-        }
-        return false;
-      });
-      const suppliedValue =
-        keyContext !== '' ? keyContext : nameContext != null ? nameContext.type.name : '';
-      const value = isObject(error.value) ? JSON.stringify(error.value) : error.value;
-      return `Invalid value "${value}" supplied to "${suppliedValue}"`;
-    }
-  });
-
-  return [...new Set(err)];
-};
+export type GenericIntersectionC =
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  | rt.IntersectionC<[any, any]>
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  | rt.IntersectionC<[any, any, any]>
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  | rt.IntersectionC<[any, any, any, any]>
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  | rt.IntersectionC<[any, any, any, any, any]>;
 
 export const createPlainError = (message: string) => new Error(message);
 
@@ -57,6 +33,40 @@ export const decodeOrThrow =
   (inputValue: I) =>
     pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity));
 
+const getProps = (
+  codec:
+    | rt.HasProps
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    | rt.RecordC<rt.StringC, any>
+    | GenericIntersectionC
+): rt.Props | null => {
+  if (codec == null) {
+    return null;
+  }
+  switch (codec._tag) {
+    case 'DictionaryType':
+      if (codec.codomain.props != null) {
+        return codec.codomain.props;
+      }
+      const dTypes: rt.HasProps[] = codec.codomain.types;
+      return dTypes.reduce<rt.Props>((props, type) => Object.assign(props, getProps(type)), {});
+    case 'RefinementType':
+    case 'ReadonlyType':
+      return getProps(codec.type);
+    case 'InterfaceType':
+    case 'StrictType':
+    case 'PartialType':
+      return codec.props;
+    case 'IntersectionType':
+      const iTypes = codec.types as rt.HasProps[];
+      return iTypes.reduce<rt.Props>((props, type) => {
+        return Object.assign(props, getProps(type) as rt.Props);
+      }, {} as rt.Props) as rt.Props;
+    default:
+      return null;
+  }
+};
+
 const getExcessProps = (props: rt.Props, r: Record<string, unknown>): string[] => {
   const ex: string[] = [];
   for (const k of Object.keys(r)) {
@@ -67,15 +77,21 @@ const getExcessProps = (props: rt.Props, r: Record<string, unknown>): string[] =
   return ex;
 };
 
-export function excess<C extends rt.InterfaceType<rt.Props> | rt.PartialType<rt.Props>>(
-  codec: C
-): C {
+export function excess<
+  C extends rt.InterfaceType<rt.Props> | GenericIntersectionC | rt.PartialType<rt.Props>
+>(codec: C): C {
+  const codecProps = getProps(codec);
+
   const r = new rt.InterfaceType(
     codec.name,
     codec.is,
     (i, c) =>
       either.chain(rt.UnknownRecord.validate(i, c), (s: Record<string, unknown>) => {
-        const ex = getExcessProps(codec.props, s);
+        if (codecProps == null) {
+          return rt.failure(i, c, 'unknown codec');
+        }
+
+        const ex = getExcessProps(codecProps, s);
         return ex.length > 0
           ? rt.failure(
               i,
@@ -87,7 +103,7 @@ export function excess<C extends rt.InterfaceType<rt.Props> | rt.PartialType<rt.
           : codec.validate(i, c);
       }),
     codec.encode,
-    codec.props
+    codecProps
   );
   return r as C;
 }
diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx
index 87d53aae14e28..f0a3502fd6813 100644
--- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx
@@ -17,7 +17,7 @@ import { TestProviders } from '../../common/mock';
 import { casesStatus, useGetCasesMockState, mockCase, connectorsMock } from '../../containers/mock';
 
 import { StatusAll } from '../../../common/ui/types';
-import { CaseStatuses } from '../../../common/api';
+import { CaseSeverity, CaseStatuses } from '../../../common/api';
 import { SECURITY_SOLUTION_OWNER } from '../../../common/constants';
 import { getEmptyTagValue } from '../empty_value';
 import { useDeleteCases } from '../../containers/use_delete_cases';
@@ -560,6 +560,7 @@ describe('AllCasesListGeneric', () => {
           username: 'lknope',
         },
         description: 'Security banana Issue',
+        severity: CaseSeverity.LOW,
         duration: null,
         externalService: {
           connectorId: '123',
diff --git a/x-pack/plugins/cases/public/components/user_actions/builder.tsx b/x-pack/plugins/cases/public/components/user_actions/builder.tsx
index 5e1c11fbdd2df..019e37396a7ce 100644
--- a/x-pack/plugins/cases/public/components/user_actions/builder.tsx
+++ b/x-pack/plugins/cases/public/components/user_actions/builder.tsx
@@ -20,6 +20,10 @@ export const builderMap: UserActionBuilderMap = {
   tags: createTagsUserActionBuilder,
   title: createTitleUserActionBuilder,
   status: createStatusUserActionBuilder,
+  // TODO: Build severity user action
+  severity: () => ({
+    build: () => [],
+  }),
   pushed: createPushedUserActionBuilder,
   comment: createCommentUserActionBuilder,
   description: createDescriptionUserActionBuilder,
diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts
index 8a31d8cac2b1e..ed9e9ebd1ff8f 100644
--- a/x-pack/plugins/cases/public/containers/mock.ts
+++ b/x-pack/plugins/cases/public/containers/mock.ts
@@ -31,6 +31,7 @@ import {
   UserActionTypes,
   UserActionWithResponse,
   CommentUserAction,
+  CaseSeverity,
 } from '../../common/api';
 import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
 import { UseGetCasesState, DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases';
@@ -154,6 +155,7 @@ export const basicCase: Case = {
     fields: null,
   },
   description: 'Security banana Issue',
+  severity: CaseSeverity.LOW,
   duration: null,
   externalService: null,
   status: CaseStatuses.open,
@@ -247,6 +249,7 @@ export const mockCase: Case = {
     fields: null,
   },
   duration: null,
+  severity: CaseSeverity.LOW,
   description: 'Security banana Issue',
   externalService: null,
   status: CaseStatuses.open,
@@ -512,6 +515,7 @@ export const getUserAction = (
           description: 'a desc',
           connector: { ...getJiraConnector() },
           status: CaseStatuses.open,
+          severity: CaseSeverity.LOW,
           title: 'a title',
           tags: ['a tag'],
           settings: { syncAlerts: true },
diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts
index ab9f6a4305800..714c8199d11a5 100644
--- a/x-pack/plugins/cases/server/client/cases/create.ts
+++ b/x-pack/plugins/cases/server/client/cases/create.ts
@@ -14,12 +14,13 @@ import { SavedObjectsUtils } from '@kbn/core/server';
 
 import {
   throwErrors,
-  excess,
   CaseResponseRt,
   CaseResponse,
   CasePostRequest,
   ActionTypes,
   CasePostRequestRt,
+  excess,
+  CaseSeverity,
 } from '../../../common/api';
 import { MAX_TITLE_LENGTH } from '../../../common/constants';
 import { isInvalidTag } from '../../../common/utils/validators';
@@ -85,7 +86,7 @@ export const create = async (
       unsecuredSavedObjectsClient,
       caseId: newCase.id,
       user,
-      payload: query,
+      payload: { ...query, severity: query.severity ?? CaseSeverity.LOW },
       owner: newCase.attributes.owner,
     });
 
diff --git a/x-pack/plugins/cases/server/client/cases/mock.ts b/x-pack/plugins/cases/server/client/cases/mock.ts
index 69a5f2d3a587b..4c0698b209bef 100644
--- a/x-pack/plugins/cases/server/client/cases/mock.ts
+++ b/x-pack/plugins/cases/server/client/cases/mock.ts
@@ -240,6 +240,7 @@ export const userActions: CaseUserActionsResponse = [
       },
       settings: { syncAlerts: true },
       status: 'open',
+      severity: 'low',
       owner: SECURITY_SOLUTION_OWNER,
     },
     action_id: 'fd830c60-6646-11eb-a291-51bf6b175a53',
diff --git a/x-pack/plugins/cases/server/client/utils.test.ts b/x-pack/plugins/cases/server/client/utils.test.ts
index 24e1135020a88..88140658c2b2b 100644
--- a/x-pack/plugins/cases/server/client/utils.test.ts
+++ b/x-pack/plugins/cases/server/client/utils.test.ts
@@ -5,9 +5,6 @@
  * 2.0.
  */
 
-import { CaseConnector, ConnectorTypes } from '../../common/api';
-import { newCase } from '../routes/api/__mocks__/request_responses';
-import { transformNewCase } from '../common/utils';
 import { buildRangeFilter, sortToSnake } from './utils';
 import { toElasticsearchQuery } from '@kbn/es-query';
 
@@ -38,74 +35,6 @@ describe('utils', () => {
     });
   });
 
-  describe('transformNewCase', () => {
-    beforeAll(() => {
-      jest.useFakeTimers('modern');
-      jest.setSystemTime(new Date('2020-04-09T09:43:51.778Z'));
-    });
-
-    afterAll(() => {
-      jest.useRealTimers();
-    });
-
-    const connector: CaseConnector = {
-      id: '123',
-      name: 'My connector',
-      type: ConnectorTypes.jira,
-      fields: { issueType: 'Task', priority: 'High', parent: null },
-    };
-    it('transform correctly', () => {
-      const myCase = {
-        newCase: { ...newCase, connector },
-        user: {
-          email: 'elastic@elastic.co',
-          full_name: 'Elastic',
-          username: 'elastic',
-        },
-      };
-
-      const res = transformNewCase(myCase);
-
-      expect(res).toMatchInlineSnapshot(`
-        Object {
-          "closed_at": null,
-          "closed_by": null,
-          "connector": Object {
-            "fields": Object {
-              "issueType": "Task",
-              "parent": null,
-              "priority": "High",
-            },
-            "id": "123",
-            "name": "My connector",
-            "type": ".jira",
-          },
-          "created_at": "2020-04-09T09:43:51.778Z",
-          "created_by": Object {
-            "email": "elastic@elastic.co",
-            "full_name": "Elastic",
-            "username": "elastic",
-          },
-          "description": "A description",
-          "duration": null,
-          "external_service": null,
-          "owner": "securitySolution",
-          "settings": Object {
-            "syncAlerts": true,
-          },
-          "status": "open",
-          "tags": Array [
-            "new",
-            "case",
-          ],
-          "title": "My new case",
-          "updated_at": null,
-          "updated_by": null,
-        }
-      `);
-    });
-  });
-
   describe('buildRangeFilter', () => {
     it('returns undefined if both the from and or are undefined', () => {
       const node = buildRangeFilter({});
diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts
index 974c36bd0d8a6..918a48863cac0 100644
--- a/x-pack/plugins/cases/server/common/utils.test.ts
+++ b/x-pack/plugins/cases/server/common/utils.test.ts
@@ -9,11 +9,14 @@ import { SavedObject, SavedObjectsFindResponse } from '@kbn/core/server';
 import { makeLensEmbeddableFactory } from '@kbn/lens-plugin/server/embeddable/make_lens_embeddable_factory';
 import { SECURITY_SOLUTION_OWNER } from '../../common/constants';
 import {
+  CaseConnector,
   CaseResponse,
+  CaseSeverity,
   CommentAttributes,
   CommentRequest,
   CommentRequestUserType,
   CommentType,
+  ConnectorTypes,
 } from '../../common/api';
 import { mockCaseComments, mockCases } from '../routes/api/__fixtures__/mock_saved_objects';
 import {
@@ -29,7 +32,9 @@ import {
   extractLensReferencesFromCommentString,
   getOrUpdateLensReferences,
   asArray,
+  transformNewCase,
 } from './utils';
+import { newCase } from '../routes/api/__mocks__/request_responses';
 
 interface CommentReference {
   ids: string[];
@@ -67,6 +72,128 @@ function createCommentFindResponse(
 }
 
 describe('common utils', () => {
+  describe('transformNewCase', () => {
+    beforeAll(() => {
+      jest.useFakeTimers('modern');
+      jest.setSystemTime(new Date('2020-04-09T09:43:51.778Z'));
+    });
+
+    afterAll(() => {
+      jest.useRealTimers();
+    });
+
+    const connector: CaseConnector = {
+      id: '123',
+      name: 'My connector',
+      type: ConnectorTypes.jira,
+      fields: { issueType: 'Task', priority: 'High', parent: null },
+    };
+
+    it('transform correctly', () => {
+      const myCase = {
+        newCase: { ...newCase, connector },
+        user: {
+          email: 'elastic@elastic.co',
+          full_name: 'Elastic',
+          username: 'elastic',
+        },
+      };
+
+      const res = transformNewCase(myCase);
+
+      expect(res).toMatchInlineSnapshot(`
+        Object {
+          "closed_at": null,
+          "closed_by": null,
+          "connector": Object {
+            "fields": Object {
+              "issueType": "Task",
+              "parent": null,
+              "priority": "High",
+            },
+            "id": "123",
+            "name": "My connector",
+            "type": ".jira",
+          },
+          "created_at": "2020-04-09T09:43:51.778Z",
+          "created_by": Object {
+            "email": "elastic@elastic.co",
+            "full_name": "Elastic",
+            "username": "elastic",
+          },
+          "description": "A description",
+          "duration": null,
+          "external_service": null,
+          "owner": "securitySolution",
+          "settings": Object {
+            "syncAlerts": true,
+          },
+          "severity": "low",
+          "status": "open",
+          "tags": Array [
+            "new",
+            "case",
+          ],
+          "title": "My new case",
+          "updated_at": null,
+          "updated_by": null,
+        }
+      `);
+    });
+
+    it('transform correctly with severity provided', () => {
+      const myCase = {
+        newCase: { ...newCase, connector, severity: CaseSeverity.MEDIUM },
+        user: {
+          email: 'elastic@elastic.co',
+          full_name: 'Elastic',
+          username: 'elastic',
+        },
+      };
+
+      const res = transformNewCase(myCase);
+
+      expect(res).toMatchInlineSnapshot(`
+        Object {
+          "closed_at": null,
+          "closed_by": null,
+          "connector": Object {
+            "fields": Object {
+              "issueType": "Task",
+              "parent": null,
+              "priority": "High",
+            },
+            "id": "123",
+            "name": "My connector",
+            "type": ".jira",
+          },
+          "created_at": "2020-04-09T09:43:51.778Z",
+          "created_by": Object {
+            "email": "elastic@elastic.co",
+            "full_name": "Elastic",
+            "username": "elastic",
+          },
+          "description": "A description",
+          "duration": null,
+          "external_service": null,
+          "owner": "securitySolution",
+          "settings": Object {
+            "syncAlerts": true,
+          },
+          "severity": "medium",
+          "status": "open",
+          "tags": Array [
+            "new",
+            "case",
+          ],
+          "title": "My new case",
+          "updated_at": null,
+          "updated_by": null,
+        }
+      `);
+    });
+  });
+
   describe('transformCases', () => {
     it('transforms correctly', () => {
       const casesMap = new Map<string, CaseResponse>(
@@ -110,6 +237,7 @@ describe('common utils', () => {
               "settings": Object {
                 "syncAlerts": true,
               },
+              "severity": "low",
               "status": "open",
               "tags": Array [
                 "defacement",
@@ -149,6 +277,7 @@ describe('common utils', () => {
               "settings": Object {
                 "syncAlerts": true,
               },
+              "severity": "low",
               "status": "open",
               "tags": Array [
                 "Data Destruction",
@@ -192,6 +321,7 @@ describe('common utils', () => {
               "settings": Object {
                 "syncAlerts": true,
               },
+              "severity": "low",
               "status": "open",
               "tags": Array [
                 "LOLBins",
@@ -239,6 +369,7 @@ describe('common utils', () => {
               "settings": Object {
                 "syncAlerts": true,
               },
+              "severity": "low",
               "status": "closed",
               "tags": Array [
                 "LOLBins",
@@ -303,6 +434,7 @@ describe('common utils', () => {
           "settings": Object {
             "syncAlerts": true,
           },
+          "severity": "low",
           "status": "open",
           "tags": Array [
             "LOLBins",
@@ -358,6 +490,7 @@ describe('common utils', () => {
           "settings": Object {
             "syncAlerts": true,
           },
+          "severity": "low",
           "status": "open",
           "tags": Array [
             "LOLBins",
@@ -436,6 +569,7 @@ describe('common utils', () => {
           "settings": Object {
             "syncAlerts": true,
           },
+          "severity": "low",
           "status": "open",
           "tags": Array [
             "LOLBins",
@@ -489,6 +623,7 @@ describe('common utils', () => {
           "settings": Object {
             "syncAlerts": true,
           },
+          "severity": "low",
           "status": "open",
           "tags": Array [
             "defacement",
diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts
index 11e77c5eb4579..bc8dbf8a6e842 100644
--- a/x-pack/plugins/cases/server/common/utils.ts
+++ b/x-pack/plugins/cases/server/common/utils.ts
@@ -19,6 +19,7 @@ import {
   CaseAttributes,
   CasePostRequest,
   CaseResponse,
+  CaseSeverity,
   CasesFindResponse,
   CaseStatuses,
   CommentAttributes,
@@ -56,6 +57,7 @@ export const transformNewCase = ({
 }): CaseAttributes => ({
   ...newCase,
   duration: null,
+  severity: newCase.severity ?? CaseSeverity.LOW,
   closed_at: null,
   closed_by: null,
   created_at: new Date().toISOString(),
diff --git a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts
index cc45ef0e2d069..77e1a64012c6d 100644
--- a/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts
+++ b/x-pack/plugins/cases/server/routes/api/__fixtures__/mock_saved_objects.ts
@@ -8,6 +8,7 @@
 import { SavedObject } from '@kbn/core/server';
 import {
   CaseAttributes,
+  CaseSeverity,
   CaseStatuses,
   CommentAttributes,
   CommentType,
@@ -34,6 +35,7 @@ export const mockCases: Array<SavedObject<CaseAttributes>> = [
         email: 'testemail@elastic.co',
         username: 'elastic',
       },
+      severity: CaseSeverity.LOW,
       duration: null,
       description: 'This is a brand new case of a bad meanie defacing data',
       external_service: null,
@@ -73,6 +75,7 @@ export const mockCases: Array<SavedObject<CaseAttributes>> = [
         email: 'testemail@elastic.co',
         username: 'elastic',
       },
+      severity: CaseSeverity.LOW,
       duration: null,
       description: 'Oh no, a bad meanie destroying data!',
       external_service: null,
@@ -112,6 +115,7 @@ export const mockCases: Array<SavedObject<CaseAttributes>> = [
         email: 'testemail@elastic.co',
         username: 'elastic',
       },
+      severity: CaseSeverity.LOW,
       duration: null,
       description: 'Oh no, a bad meanie going LOLBins all over the place!',
       external_service: null,
@@ -155,6 +159,7 @@ export const mockCases: Array<SavedObject<CaseAttributes>> = [
         email: 'testemail@elastic.co',
         username: 'elastic',
       },
+      severity: CaseSeverity.LOW,
       duration: null,
       description: 'Oh no, a bad meanie going LOLBins all over the place!',
       external_service: null,
diff --git a/x-pack/plugins/cases/server/saved_object_types/cases.ts b/x-pack/plugins/cases/server/saved_object_types/cases.ts
index ea68fc24f60ca..9b2ea975c4dcd 100644
--- a/x-pack/plugins/cases/server/saved_object_types/cases.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/cases.ts
@@ -152,6 +152,9 @@ export const createCaseSavedObjectType = (
           },
         },
       },
+      severity: {
+        type: 'keyword',
+      },
     },
   },
   migrations: caseMigrations,
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts
index 70e0e91caa57f..b4d3421643a41 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.test.ts
@@ -9,13 +9,14 @@ import { SavedObjectSanitizedDoc } from '@kbn/core/server';
 import {
   CaseAttributes,
   CaseFullExternalService,
+  CaseSeverity,
   ConnectorTypes,
   NONE_CONNECTOR_ID,
 } from '../../../common/api';
 import { CASE_SAVED_OBJECT } from '../../../common/constants';
 import { getNoneCaseConnector } from '../../common/utils';
 import { createExternalService, ESCaseConnectorWithId } from '../../services/test_utils';
-import { addDuration, caseConnectorIdMigration, removeCaseType } from './cases';
+import { addDuration, addSeverity, caseConnectorIdMigration, removeCaseType } from './cases';
 
 // eslint-disable-next-line @typescript-eslint/naming-convention
 const create_7_14_0_case = ({
@@ -496,4 +497,45 @@ describe('case migrations', () => {
       });
     });
   });
+
+  describe('add severity', () => {
+    it('adds the severity correctly when none is present', () => {
+      const doc = {
+        id: '123',
+        attributes: {
+          created_at: '2021-11-23T19:00:00Z',
+          closed_at: '2021-11-23T19:02:00Z',
+        },
+        type: 'abc',
+        references: [],
+      } as unknown as SavedObjectSanitizedDoc<CaseAttributes>;
+      expect(addSeverity(doc)).toEqual({
+        ...doc,
+        attributes: {
+          ...doc.attributes,
+          severity: CaseSeverity.LOW,
+        },
+      });
+    });
+
+    it('keeps the existing value if the field already exists', () => {
+      const doc = {
+        id: '123',
+        attributes: {
+          severity: CaseSeverity.CRITICAL,
+          created_at: '2021-11-23T19:00:00Z',
+          closed_at: '2021-11-23T19:02:00Z',
+        },
+        type: 'abc',
+        references: [],
+      } as unknown as SavedObjectSanitizedDoc<CaseAttributes>;
+      expect(addSeverity(doc)).toEqual({
+        ...doc,
+        attributes: {
+          ...doc.attributes,
+          severity: CaseSeverity.CRITICAL,
+        },
+      });
+    });
+  });
 });
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts
index 91a462c5c8053..c4961f742abc7 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/cases.ts
@@ -11,7 +11,7 @@ import { cloneDeep, unset } from 'lodash';
 import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc } from '@kbn/core/server';
 import { addOwnerToSO, SanitizedCaseOwner } from '.';
 import { ESConnectorFields } from '../../services';
-import { CaseAttributes, ConnectorTypes } from '../../../common/api';
+import { CaseAttributes, CaseSeverity, ConnectorTypes } from '../../../common/api';
 import {
   CONNECTOR_ID_REFERENCE_NAME,
   PUSH_CONNECTOR_ID_REFERENCE_NAME,
@@ -21,6 +21,7 @@ import {
   transformPushConnectorIdToReference,
 } from './user_actions/connector_id';
 import { CASE_TYPE_INDIVIDUAL } from './constants';
+import { pipeMigrations } from './utils';
 
 interface UnsanitizedCaseConnector {
   connector_id: string;
@@ -114,6 +115,13 @@ export const addDuration = (
   return { ...doc, attributes: { ...doc.attributes, duration }, references: doc.references ?? [] };
 };
 
+export const addSeverity = (
+  doc: SavedObjectUnsanitizedDoc<CaseAttributes>
+): SavedObjectSanitizedDoc<CaseAttributes> => {
+  const severity = doc.attributes.severity ?? CaseSeverity.LOW;
+  return { ...doc, attributes: { ...doc.attributes, severity }, references: doc.references ?? [] };
+};
+
 export const caseMigrations = {
   '7.10.0': (
     doc: SavedObjectUnsanitizedDoc<UnsanitizedCaseConnector>
@@ -175,5 +183,5 @@ export const caseMigrations = {
   },
   '7.15.0': caseConnectorIdMigration,
   '8.1.0': removeCaseType,
-  '8.3.0': addDuration,
+  '8.3.0': pipeMigrations(addDuration, addSeverity),
 };
diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/utils.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/utils.ts
index 65c1d42271845..8996f89155949 100644
--- a/x-pack/plugins/cases/server/saved_object_types/migrations/utils.ts
+++ b/x-pack/plugins/cases/server/saved_object_types/migrations/utils.ts
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import { LogMeta, SavedObjectMigrationContext } from '@kbn/core/server';
+import { LogMeta, SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from '@kbn/core/server';
 
 interface MigrationLogMeta extends LogMeta {
   migrations: {
@@ -39,3 +39,10 @@ export function logError({
     }
   );
 }
+
+type CaseMigration<T> = (doc: SavedObjectUnsanitizedDoc<T>) => SavedObjectUnsanitizedDoc<T>;
+
+export function pipeMigrations<T>(...migrations: Array<CaseMigration<T>>): CaseMigration<T> {
+  return (doc: SavedObjectUnsanitizedDoc<T>) =>
+    migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc);
+}
diff --git a/x-pack/plugins/cases/server/services/cases/index.test.ts b/x-pack/plugins/cases/server/services/cases/index.test.ts
index 84c580c8800e3..826a8d06e97f2 100644
--- a/x-pack/plugins/cases/server/services/cases/index.test.ts
+++ b/x-pack/plugins/cases/server/services/cases/index.test.ts
@@ -166,6 +166,7 @@ describe('CasesService', () => {
             "settings": Object {
               "syncAlerts": true,
             },
+            "severity": "low",
             "status": "open",
             "tags": Array [
               "defacement",
@@ -519,6 +520,7 @@ describe('CasesService', () => {
             "settings": Object {
               "syncAlerts": true,
             },
+            "severity": "low",
             "status": "open",
             "tags": Array [
               "defacement",
diff --git a/x-pack/plugins/cases/server/services/test_utils.ts b/x-pack/plugins/cases/server/services/test_utils.ts
index 617dedd368ab3..ff86783ae8e9c 100644
--- a/x-pack/plugins/cases/server/services/test_utils.ts
+++ b/x-pack/plugins/cases/server/services/test_utils.ts
@@ -10,15 +10,18 @@ import { ACTION_SAVED_OBJECT_TYPE } from '@kbn/actions-plugin/server';
 import { ESConnectorFields } from '.';
 import { CONNECTOR_ID_REFERENCE_NAME, PUSH_CONNECTOR_ID_REFERENCE_NAME } from '../common/constants';
 import {
+  CaseAttributes,
   CaseConnector,
   CaseExternalServiceBasic,
   CaseFullExternalService,
+  CaseSeverity,
   CaseStatuses,
   ConnectorTypes,
   NONE_CONNECTOR_ID,
 } from '../../common/api';
 import { CASE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../common/constants';
 import { ESCaseAttributes, ExternalServicesWithoutConnectorId } from './cases/types';
+import { getNoneCaseConnector } from '../common/utils';
 
 /**
  * This is only a utility interface to help with constructing test cases. After the migration, the ES format will no longer
@@ -96,7 +99,7 @@ export const createExternalService = (
   ...overrides,
 });
 
-export const basicCaseFields = {
+export const basicCaseFields: CaseAttributes = {
   closed_at: null,
   closed_by: null,
   created_at: '2019-11-25T21:54:48.952Z',
@@ -105,6 +108,7 @@ export const basicCaseFields = {
     email: 'testemail@elastic.co',
     username: 'elastic',
   },
+  severity: CaseSeverity.LOW,
   duration: null,
   description: 'This is a brand new case of a bad meanie defacing data',
   title: 'Super Bad Security Issue',
@@ -116,6 +120,8 @@ export const basicCaseFields = {
     email: 'testemail@elastic.co',
     username: 'elastic',
   },
+  connector: getNoneCaseConnector(),
+  external_service: null,
   settings: {
     syncAlerts: true,
   },
diff --git a/x-pack/plugins/cases/server/services/user_actions/builder_factory.test.ts b/x-pack/plugins/cases/server/services/user_actions/builder_factory.test.ts
index 2e2a9e905bb7e..ab349d690edef 100644
--- a/x-pack/plugins/cases/server/services/user_actions/builder_factory.test.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/builder_factory.test.ts
@@ -9,6 +9,7 @@ import { SECURITY_SOLUTION_OWNER } from '../../../common';
 import {
   Actions,
   ActionTypes,
+  CaseSeverity,
   CaseStatuses,
   CommentType,
   ConnectorTypes,
@@ -340,6 +341,40 @@ describe('UserActionBuilder', () => {
     `);
   });
 
+  it('builds a severity user action correctly', () => {
+    const builder = builderFactory.getBuilder(ActionTypes.severity)!;
+    const userAction = builder.build({
+      payload: { severity: CaseSeverity.LOW },
+      ...commonArgs,
+    });
+
+    expect(userAction).toMatchInlineSnapshot(`
+      Object {
+        "attributes": Object {
+          "action": "update",
+          "created_at": "2022-01-09T22:00:00.000Z",
+          "created_by": Object {
+            "email": "elastic@elastic.co",
+            "full_name": "Elastic User",
+            "username": "elastic",
+          },
+          "owner": "securitySolution",
+          "payload": Object {
+            "severity": "low",
+          },
+          "type": "severity",
+        },
+        "references": Array [
+          Object {
+            "id": "123",
+            "name": "associated-cases",
+            "type": "cases",
+          },
+        ],
+      }
+    `);
+  });
+
   it('builds a settings user action correctly', () => {
     const builder = builderFactory.getBuilder(ActionTypes.settings)!;
     const userAction = builder.build({
@@ -413,6 +448,7 @@ describe('UserActionBuilder', () => {
             "settings": Object {
               "syncAlerts": true,
             },
+            "severity": "low",
             "status": "open",
             "tags": Array [
               "sir",
diff --git a/x-pack/plugins/cases/server/services/user_actions/builder_factory.ts b/x-pack/plugins/cases/server/services/user_actions/builder_factory.ts
index 5d5f33c2ae4f5..510b6d12b1fa1 100644
--- a/x-pack/plugins/cases/server/services/user_actions/builder_factory.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/builder_factory.ts
@@ -17,6 +17,7 @@ import { TagsUserActionBuilder } from './builders/tags';
 import { SettingsUserActionBuilder } from './builders/settings';
 import { DeleteCaseUserActionBuilder } from './builders/delete_case';
 import { UserActionBuilder } from './abstract_builder';
+import { SeverityUserActionBuilder } from './builders/severity';
 
 const builderMap = {
   title: TitleUserActionBuilder,
@@ -27,6 +28,7 @@ const builderMap = {
   pushed: PushedUserActionBuilder,
   tags: TagsUserActionBuilder,
   status: StatusUserActionBuilder,
+  severity: SeverityUserActionBuilder,
   settings: SettingsUserActionBuilder,
   delete_case: DeleteCaseUserActionBuilder,
 };
diff --git a/x-pack/plugins/cases/server/services/user_actions/builders/severity.ts b/x-pack/plugins/cases/server/services/user_actions/builders/severity.ts
new file mode 100644
index 0000000000000..4abd5856972b4
--- /dev/null
+++ b/x-pack/plugins/cases/server/services/user_actions/builders/severity.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { Actions, ActionTypes } from '../../../../common/api';
+import { UserActionBuilder } from '../abstract_builder';
+import { UserActionParameters, BuilderReturnValue } from '../types';
+
+export class SeverityUserActionBuilder extends UserActionBuilder {
+  build(args: UserActionParameters<'severity'>): BuilderReturnValue {
+    return this.buildCommonUserAction({
+      ...args,
+      action: Actions.update,
+      valueKey: 'severity',
+      value: args.payload.severity,
+      type: ActionTypes.severity,
+    });
+  }
+}
diff --git a/x-pack/plugins/cases/server/services/user_actions/index.test.ts b/x-pack/plugins/cases/server/services/user_actions/index.test.ts
index eb1b57622d24d..44e91bcae09d3 100644
--- a/x-pack/plugins/cases/server/services/user_actions/index.test.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/index.test.ts
@@ -13,6 +13,7 @@ import { ACTION_SAVED_OBJECT_TYPE } from '@kbn/actions-plugin/server';
 import {
   Actions,
   ActionTypes,
+  CaseSeverity,
   CaseStatuses,
   CaseUserActionAttributes,
   ConnectorUserAction,
@@ -107,6 +108,7 @@ const createCaseUserAction = (): SavedObject<CaseUserActionAttributes> => {
         description: 'a desc',
         settings: { syncAlerts: false },
         status: CaseStatuses.open,
+        severity: CaseSeverity.LOW,
         tags: [],
         owner: SECURITY_SOLUTION_OWNER,
       },
@@ -447,6 +449,7 @@ describe('CaseUserActionService', () => {
             payload: casePayload,
             type: ActionTypes.create_case,
           });
+
           expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith(
             'cases-user-actions',
             {
@@ -477,6 +480,7 @@ describe('CaseUserActionService', () => {
                 owner: 'securitySolution',
                 settings: { syncAlerts: true },
                 status: 'open',
+                severity: 'low',
                 tags: ['sir'],
                 title: 'Case SIR',
               },
@@ -517,6 +521,33 @@ describe('CaseUserActionService', () => {
           });
         });
 
+        describe('severity', () => {
+          it('creates an update severity user action', async () => {
+            await service.createUserAction({
+              ...commonArgs,
+              payload: { severity: CaseSeverity.MEDIUM },
+              type: ActionTypes.severity,
+            });
+
+            expect(unsecuredSavedObjectsClient.create).toHaveBeenCalledWith(
+              'cases-user-actions',
+              {
+                action: Actions.update,
+                created_at: '2022-01-09T22:00:00.000Z',
+                created_by: {
+                  email: 'elastic@elastic.co',
+                  full_name: 'Elastic User',
+                  username: 'elastic',
+                },
+                type: 'severity',
+                owner: 'securitySolution',
+                payload: { severity: 'medium' },
+              },
+              { references: [{ id: '123', name: 'associated-cases', type: 'cases' }] }
+            );
+          });
+        });
+
         describe('push', () => {
           it('creates a push user action', async () => {
             await service.createUserAction({
@@ -801,6 +832,30 @@ describe('CaseUserActionService', () => {
             references: [{ id: '2', name: 'associated-cases', type: 'cases' }],
             type: 'cases-user-actions',
           },
+          {
+            attributes: {
+              action: 'update',
+              created_at: '2022-01-09T22:00:00.000Z',
+              created_by: {
+                email: 'elastic@elastic.co',
+                full_name: 'Elastic User',
+                username: 'elastic',
+              },
+              owner: 'securitySolution',
+              payload: {
+                severity: 'critical',
+              },
+              type: 'severity',
+            },
+            references: [
+              {
+                id: '2',
+                name: 'associated-cases',
+                type: 'cases',
+              },
+            ],
+            type: 'cases-user-actions',
+          },
         ]);
       });
     });
diff --git a/x-pack/plugins/cases/server/services/user_actions/mocks.ts b/x-pack/plugins/cases/server/services/user_actions/mocks.ts
index c745c040ac2ce..bc35f98bf926e 100644
--- a/x-pack/plugins/cases/server/services/user_actions/mocks.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/mocks.ts
@@ -7,7 +7,7 @@
 
 import { CASE_SAVED_OBJECT } from '../../../common/constants';
 import { SECURITY_SOLUTION_OWNER } from '../../../common';
-import { CaseStatuses, CommentType, ConnectorTypes } from '../../../common/api';
+import { CaseSeverity, CaseStatuses, CommentType, ConnectorTypes } from '../../../common/api';
 import { createCaseSavedObjectResponse } from '../test_utils';
 import { transformSavedObjectToExternalModel } from '../cases/transform';
 
@@ -30,6 +30,7 @@ export const casePayload = {
     },
   },
   settings: { syncAlerts: true },
+  severity: CaseSeverity.LOW,
   owner: SECURITY_SOLUTION_OWNER,
 };
 
@@ -69,6 +70,7 @@ export const updatedCases = [
       description: 'updated desc',
       tags: ['one', 'two'],
       settings: { syncAlerts: false },
+      severity: CaseSeverity.CRITICAL,
     },
     references: [],
   },
diff --git a/x-pack/plugins/cases/server/services/user_actions/types.ts b/x-pack/plugins/cases/server/services/user_actions/types.ts
index f681a9186181c..a60dee552a6be 100644
--- a/x-pack/plugins/cases/server/services/user_actions/types.ts
+++ b/x-pack/plugins/cases/server/services/user_actions/types.ts
@@ -9,6 +9,7 @@ import { SavedObjectReference } from '@kbn/core/server';
 import {
   CasePostRequest,
   CaseSettings,
+  CaseSeverity,
   CaseStatuses,
   CommentUserAction,
   ConnectorUserAction,
@@ -28,6 +29,9 @@ export interface BuilderParameters {
   status: {
     parameters: { payload: { status: CaseStatuses } };
   };
+  severity: {
+    parameters: { payload: { severity: CaseSeverity } };
+  };
   tags: {
     parameters: { payload: { tags: string[] } };
   };
diff --git a/x-pack/test/cases_api_integration/common/lib/mock.ts b/x-pack/test/cases_api_integration/common/lib/mock.ts
index 34f1d4a9273d2..08f30f8df024e 100644
--- a/x-pack/test/cases_api_integration/common/lib/mock.ts
+++ b/x-pack/test/cases_api_integration/common/lib/mock.ts
@@ -17,6 +17,7 @@ import {
   CaseStatuses,
   CommentRequest,
   CommentRequestActionsType,
+  CaseSeverity,
 } from '@kbn/cases-plugin/common/api';
 
 export const defaultUser = { email: null, full_name: null, username: 'elastic' };
@@ -86,6 +87,7 @@ export const postCaseResp = (
   ...(id != null ? { id } : {}),
   comments: [],
   duration: null,
+  severity: req.severity ?? CaseSeverity.LOW,
   totalAlerts: 0,
   totalComment: 0,
   closed_by: null,
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts
index 44da07a845ff7..2ce441c37e687 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts
@@ -27,6 +27,7 @@ import {
   CommentUserAction,
   CreateCaseUserAction,
   CaseStatuses,
+  CaseSeverity,
 } from '@kbn/cases-plugin/common/api';
 import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib';
 import {
@@ -204,6 +205,10 @@ const expectExportToHaveCaseSavedObject = (
   expect(createdCaseSO.attributes.connector.name).to.eql(caseRequest.connector.name);
   expect(createdCaseSO.attributes.connector.fields).to.eql([]);
   expect(createdCaseSO.attributes.settings).to.eql(caseRequest.settings);
+  expect(createdCaseSO.attributes.status).to.eql(CaseStatuses.open);
+  expect(createdCaseSO.attributes.severity).to.eql(CaseSeverity.LOW);
+  expect(createdCaseSO.attributes.duration).to.eql(null);
+  expect(createdCaseSO.attributes.tags).to.eql(caseRequest.tags);
 };
 
 const expectExportToHaveUserActions = (objects: SavedObject[], caseRequest: CasePostRequest) => {
@@ -239,6 +244,7 @@ const expectCaseCreateUserAction = (
   expect(restParsedCreateCase).to.eql({
     ...restCreateCase,
     status: CaseStatuses.open,
+    severity: CaseSeverity.LOW,
   });
   expect(restParsedConnector).to.eql(restConnector);
 };
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts
index 3c43ac1932986..4d4b9d45b6717 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts
@@ -369,7 +369,7 @@ export default function createGetTests({ getService }: FtrProviderContext) {
       });
     });
 
-    describe('8.3.0 adding duration', () => {
+    describe('8.3.0', () => {
       before(async () => {
         await kibanaServer.importExport.load(
           'x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_duration.json'
@@ -383,34 +383,48 @@ export default function createGetTests({ getService }: FtrProviderContext) {
         await deleteAllCaseItems(es);
       });
 
-      it('calculates the correct duration for closed cases', async () => {
-        const caseInfo = await getCase({
-          supertest,
-          caseId: '4537b380-a512-11ec-b92f-859b9e89e434',
+      describe('adding duration', () => {
+        it('calculates the correct duration for closed cases', async () => {
+          const caseInfo = await getCase({
+            supertest,
+            caseId: '4537b380-a512-11ec-b92f-859b9e89e434',
+          });
+
+          expect(caseInfo).to.have.property('duration');
+          expect(caseInfo.duration).to.be(120);
         });
 
-        expect(caseInfo).to.have.property('duration');
-        expect(caseInfo.duration).to.be(120);
-      });
+        it('sets the duration to null to open cases', async () => {
+          const caseInfo = await getCase({
+            supertest,
+            caseId: '7537b580-a512-11ec-b94f-85979e89e434',
+          });
 
-      it('sets the duration to null to open cases', async () => {
-        const caseInfo = await getCase({
-          supertest,
-          caseId: '7537b580-a512-11ec-b94f-85979e89e434',
+          expect(caseInfo).to.have.property('duration');
+          expect(caseInfo.duration).to.be(null);
         });
 
-        expect(caseInfo).to.have.property('duration');
-        expect(caseInfo.duration).to.be(null);
-      });
+        it('sets the duration to null to in-progress cases', async () => {
+          const caseInfo = await getCase({
+            supertest,
+            caseId: '1537b580-a512-11ec-b94f-85979e89e434',
+          });
 
-      it('sets the duration to null to in-progress cases', async () => {
-        const caseInfo = await getCase({
-          supertest,
-          caseId: '1537b580-a512-11ec-b94f-85979e89e434',
+          expect(caseInfo).to.have.property('duration');
+          expect(caseInfo.duration).to.be(null);
         });
+      });
 
-        expect(caseInfo).to.have.property('duration');
-        expect(caseInfo.duration).to.be(null);
+      describe('add severity', () => {
+        it('adds the severity field for existing documents', async () => {
+          const caseInfo = await getCase({
+            supertest,
+            caseId: '4537b380-a512-11ec-b92f-859b9e89e434',
+          });
+
+          expect(caseInfo).to.have.property('severity');
+          expect(caseInfo.severity).to.be('low');
+        });
       });
     });
   });
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts
index 9ef1c3d5655e4..80dffef7cd3ee 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/patch_cases.ts
@@ -10,6 +10,7 @@ import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils';
 
 import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '@kbn/security-solution-plugin/common/constants';
 import {
+  CaseSeverity,
   CasesResponse,
   CaseStatuses,
   CommentType,
@@ -170,6 +171,34 @@ export default ({ getService }: FtrProviderContext): void => {
         });
       });
 
+      it('should patch the severity of a case correctly', async () => {
+        const postedCase = await createCase(supertest, postCaseReq);
+
+        // the default severity
+        expect(postedCase.severity).equal(CaseSeverity.LOW);
+
+        const patchedCases = await updateCase({
+          supertest,
+          params: {
+            cases: [
+              {
+                id: postedCase.id,
+                version: postedCase.version,
+                severity: CaseSeverity.MEDIUM,
+              },
+            ],
+          },
+        });
+
+        const data = removeServerGeneratedPropertiesFromCase(patchedCases[0]);
+
+        expect(data).to.eql({
+          ...postCaseResp(),
+          severity: CaseSeverity.MEDIUM,
+          updated_by: defaultUser,
+        });
+      });
+
       it('should patch a case with new connector', async () => {
         const postedCase = await createCase(supertest, postCaseReq);
         const patchedCases = await updateCase({
@@ -297,6 +326,22 @@ export default ({ getService }: FtrProviderContext): void => {
         });
       });
 
+      it('400s when a wrong severity value is passed', async () => {
+        await updateCase({
+          supertest,
+          params: {
+            cases: [
+              {
+                version: 'version',
+                // @ts-expect-error
+                severity: 'wont-do',
+              },
+            ],
+          },
+          expectedHttpCode: 400,
+        });
+      });
+
       it('400s when id is missing', async () => {
         await updateCase({
           supertest,
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts
index 9cd986a032b24..d4b52ff6f3394 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/post_case.ts
@@ -12,6 +12,7 @@ import {
   ConnectorTypes,
   ConnectorJiraTypeFields,
   CaseStatuses,
+  CaseSeverity,
 } from '@kbn/cases-plugin/common/api';
 import { getPostCaseRequest, postCaseResp, defaultUser } from '../../../../common/lib/mock';
 import {
@@ -102,6 +103,32 @@ export default ({ getService }: FtrProviderContext): void => {
         );
       });
 
+      it('should post a case without severity', async () => {
+        const postedCase = await createCase(supertest, getPostCaseRequest());
+        const data = removeServerGeneratedPropertiesFromCase(postedCase);
+
+        expect(data).to.eql(postCaseResp(null, getPostCaseRequest()));
+      });
+
+      it('should post a case with severity', async () => {
+        const postedCase = await createCase(
+          supertest,
+          getPostCaseRequest({
+            severity: CaseSeverity.HIGH,
+          })
+        );
+        const data = removeServerGeneratedPropertiesFromCase(postedCase);
+
+        expect(data).to.eql(
+          postCaseResp(
+            null,
+            getPostCaseRequest({
+              severity: CaseSeverity.HIGH,
+            })
+          )
+        );
+      });
+
       it('should create a user action when creating a case', async () => {
         const postedCase = await createCase(supertest, getPostCaseRequest());
         const userActions = await getCaseUserActions({ supertest, caseID: postedCase.id });
@@ -122,6 +149,7 @@ export default ({ getService }: FtrProviderContext): void => {
             settings: postedCase.settings,
             owner: postedCase.owner,
             status: CaseStatuses.open,
+            severity: CaseSeverity.LOW,
           },
         });
       });
@@ -207,6 +235,11 @@ export default ({ getService }: FtrProviderContext): void => {
         await supertest.post(CASES_URL).set('kbn-xsrf', 'true').send(caseWithoutTags).expect(400);
       });
 
+      it('400s when passing a wrong severity value', async () => {
+        // @ts-expect-error
+        await createCase(supertest, { ...getPostCaseRequest(), severity: 'very-severe' }, 400);
+      });
+
       it('400s if you passing status for a new case', async () => {
         const req = getPostCaseRequest();
 
diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts
index 283e6b0c5301b..aacb5f6c8ae17 100644
--- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts
+++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_all_user_actions.ts
@@ -9,6 +9,7 @@ import expect from '@kbn/expect';
 
 import {
   CaseResponse,
+  CaseSeverity,
   CaseStatuses,
   CommentType,
   ConnectorTypes,
@@ -106,6 +107,30 @@ export default ({ getService }: FtrProviderContext): void => {
       expect(statusUserAction.payload).to.eql({ status: 'closed' });
     });
 
+    it('creates a severity update user action when changing the severity', async () => {
+      const theCase = await createCase(supertest, postCaseReq);
+      await updateCase({
+        supertest,
+        params: {
+          cases: [
+            {
+              id: theCase.id,
+              version: theCase.version,
+              severity: CaseSeverity.HIGH,
+            },
+          ],
+        },
+      });
+
+      const userActions = await getCaseUserActions({ supertest, caseID: theCase.id });
+      const statusUserAction = userActions[1];
+
+      expect(userActions.length).to.eql(2);
+      expect(statusUserAction.type).to.eql('severity');
+      expect(statusUserAction.action).to.eql('update');
+      expect(statusUserAction.payload).to.eql({ severity: 'high' });
+    });
+
     it('creates a connector update user action', async () => {
       const newConnector = {
         id: '123',