From 51e8ad0f98f2c9f46ba657904e49b8f499ae403e Mon Sep 17 00:00:00 2001
From: Ievgen Sorokopud <ievgen.sorokopud@elastic.co>
Date: Mon, 9 Dec 2024 20:21:16 +0100
Subject: [PATCH] [Rules migration] Add sorting functionality to rules
 migration table (#11379) (#203396)

## Summary

[Internal link](https://github.com/elastic/security-team/issues/10820)
to the feature details

These changes add sorting functionality to the migration rules table. It
is possible to sort migration rules by next columns: `Updated`, `Name`,
`Status`, `Risk Score`, `Severity` and `Author`.

### Other changes

Next fixes and adjustments were also implemented as part of this PR:
* `Installed` status in migration rules table to indicate whether the
rule was installed
* Rules selection and installation of selected rules
* Disable selection for not fully translated rules
* `Author` column to show whether the translated rule matched one of the
existing Elastic prebuilt rules
* `Install and enable` and `Install without enabling` buttons within the
migration rule details flyout
---
 .../kbn-index-adapter/src/field_maps/types.ts |   1 +
 .../model/api/rules/rule_migration.gen.ts     |  10 +-
 .../api/rules/rule_migration.schema.yaml      |  29 +++-
 .../common/siem_migrations/rules/utils.ts     |   6 +-
 .../public/siem_migrations/rules/api/index.ts |  23 +++-
 .../components/rule_details_flyout/index.tsx  |   3 +-
 .../components/rules_table/bulk_actions.tsx   |  17 ++-
 .../rules/components/rules_table/index.tsx    | 119 ++++++++++++++--
 .../components/rules_table/translations.ts    |  28 ++++
 .../components/rules_table_columns/author.tsx |  39 ++++++
 .../components/rules_table_columns/index.tsx  |   1 +
 .../components/rules_table_columns/name.tsx   |  11 +-
 .../rules_table_columns/risk_score.tsx        |   2 +-
 .../rules_table_columns/severity.tsx          |   5 +-
 .../components/rules_table_columns/status.tsx |   4 +-
 .../rules_table_columns/translations.ts       |  21 +++
 .../components/status_badge/index.test.tsx    |  19 ---
 .../rules/components/status_badge/index.tsx   |  36 +++--
 .../components/status_badge/translations.ts   |  15 +++
 .../use_migration_rules_table_columns.tsx     |   2 +
 .../rules/logic/use_get_migration_rules.ts    |   2 +
 .../logic/use_install_migration_rules.ts      |   4 +-
 .../lib/siem_migrations/rules/api/get.ts      |   9 +-
 .../lib/siem_migrations/rules/api/install.ts  |   3 +-
 .../rules/api/install_translated.ts           |   1 +
 .../rules/api/util/installation.ts            |  19 ++-
 .../data/rule_migrations_data_rules_client.ts |  61 +++------
 .../rules/data/rule_migrations_field_maps.ts  |   4 +-
 .../lib/siem_migrations/rules/data/search.ts  |  42 ++++++
 .../lib/siem_migrations/rules/data/sort.ts    | 127 ++++++++++++++++++
 30 files changed, 541 insertions(+), 122 deletions(-)
 create mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx
 delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx
 create mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.ts
 create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/search.ts
 create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/sort.ts

diff --git a/packages/kbn-index-adapter/src/field_maps/types.ts b/packages/kbn-index-adapter/src/field_maps/types.ts
index 1cdafc7c61809..90fb44873a342 100644
--- a/packages/kbn-index-adapter/src/field_maps/types.ts
+++ b/packages/kbn-index-adapter/src/field_maps/types.ts
@@ -46,6 +46,7 @@ export type FieldMap<T extends string = string> = Record<
     array?: boolean;
     doc_values?: boolean;
     enabled?: boolean;
+    fields?: Record<string, { type: string }>;
     format?: string;
     ignore_above?: number;
     multi_fields?: MultiField[];
diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts
index 58944ff7f2f95..95a81d4436d8a 100644
--- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts
+++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.gen.ts
@@ -59,6 +59,8 @@ export type GetRuleMigrationRequestQuery = z.infer<typeof GetRuleMigrationReques
 export const GetRuleMigrationRequestQuery = z.object({
   page: z.coerce.number().optional(),
   per_page: z.coerce.number().optional(),
+  sort_field: NonEmptyString.optional(),
+  sort_direction: z.enum(['asc', 'desc']).optional(),
   search_term: z.string().optional(),
 });
 export type GetRuleMigrationRequestQueryInput = z.input<typeof GetRuleMigrationRequestQuery>;
@@ -154,7 +156,13 @@ export type InstallMigrationRulesRequestParamsInput = z.input<
 >;
 
 export type InstallMigrationRulesRequestBody = z.infer<typeof InstallMigrationRulesRequestBody>;
-export const InstallMigrationRulesRequestBody = z.array(NonEmptyString);
+export const InstallMigrationRulesRequestBody = z.object({
+  ids: z.array(NonEmptyString),
+  /**
+   * Indicates whether installed rules should be enabled
+   */
+  enabled: z.boolean().optional(),
+});
 export type InstallMigrationRulesRequestBodyInput = z.input<
   typeof InstallMigrationRulesRequestBody
 >;
diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml
index dff6089b2b48f..b7e495e2ea898 100644
--- a/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml
+++ b/x-pack/plugins/security_solution/common/siem_migrations/model/api/rules/rule_migration.schema.yaml
@@ -133,6 +133,19 @@ paths:
           required: false
           schema:
             type: number
+        - name: sort_field
+          in: query
+          required: false
+          schema:
+            $ref: '../../../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString'
+        - name: sort_direction
+          in: query
+          required: false
+          schema:
+            type: string
+            enum:
+              - asc
+              - desc
         - name: search_term
           in: query
           required: false
@@ -180,10 +193,18 @@ paths:
         content:
           application/json:
             schema:
-              type: array
-              items:
-                description: The rule migration id
-                $ref: '../../../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString'
+              type: object
+              required:
+                - ids
+              properties:
+                ids:
+                  type: array
+                  items:
+                    description: The rule migration id
+                    $ref: '../../../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString'
+                enabled:
+                  type: boolean
+                  description: Indicates whether installed rules should be enabled
       responses:
         200:
           description: Indicates rules migrations have been installed correctly.
diff --git a/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts b/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts
index a9b8666b19981..8763e057052b5 100644
--- a/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts
+++ b/x-pack/plugins/security_solution/common/siem_migrations/rules/utils.ts
@@ -22,13 +22,17 @@ export const isMigrationCustomRule = (rule?: ElasticRule): rule is MigrationCust
   !isMigrationPrebuiltRule(rule) &&
   !!(rule?.title && rule?.description && rule?.query && rule?.query_language);
 
-export const convertMigrationCustomRuleToSecurityRulePayload = (rule: MigrationCustomRule) => {
+export const convertMigrationCustomRuleToSecurityRulePayload = (
+  rule: MigrationCustomRule,
+  enabled: boolean
+) => {
   return {
     type: rule.query_language,
     language: rule.query_language,
     query: rule.query,
     name: rule.title,
     description: rule.description,
+    enabled,
 
     ...DEFAULT_TRANSLATION_FIELDS,
     severity: (rule.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY,
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts
index ac9e49ff861fc..57fb5d0422093 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts
@@ -120,6 +120,10 @@ export interface GetRuleMigrationParams {
   page?: number;
   /** Optional number of documents per page to retrieve */
   perPage?: number;
+  /** Optional field of the rule migration object to sort results by */
+  sortField?: string;
+  /** Optional direction to sort results by */
+  sortDirection?: 'asc' | 'desc';
   /** Optional search term to filter documents */
   searchTerm?: string;
   /** Optional AbortSignal for cancelling request */
@@ -130,12 +134,24 @@ export const getRuleMigrations = async ({
   migrationId,
   page,
   perPage,
+  sortField,
+  sortDirection,
   searchTerm,
   signal,
 }: GetRuleMigrationParams): Promise<GetRuleMigrationResponse> => {
   return KibanaServices.get().http.get<GetRuleMigrationResponse>(
     replaceParams(SIEM_RULE_MIGRATION_PATH, { migration_id: migrationId }),
-    { version: '1', query: { page, per_page: perPage, search_term: searchTerm }, signal }
+    {
+      version: '1',
+      query: {
+        page,
+        per_page: perPage,
+        sort_field: sortField,
+        sort_direction: sortDirection,
+        search_term: searchTerm,
+      },
+      signal,
+    }
   );
 };
 
@@ -163,6 +179,8 @@ export interface InstallRulesParams {
   migrationId: string;
   /** The rule ids to install */
   ids: string[];
+  /** Optional indicator to enable the installed rule */
+  enabled?: boolean;
   /** Optional AbortSignal for cancelling request */
   signal?: AbortSignal;
 }
@@ -170,11 +188,12 @@ export interface InstallRulesParams {
 export const installMigrationRules = async ({
   migrationId,
   ids,
+  enabled,
   signal,
 }: InstallRulesParams): Promise<InstallMigrationRulesResponse> => {
   return KibanaServices.get().http.post<InstallMigrationRulesResponse>(
     replaceParams(SIEM_RULE_MIGRATION_INSTALL_PATH, { migration_id: migrationId }),
-    { version: '1', body: JSON.stringify(ids), signal }
+    { version: '1', body: JSON.stringify({ ids, enabled }), signal }
   );
 };
 
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx
index 8fea17b51cb0e..9762cc578e0cc 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx
@@ -84,7 +84,8 @@ export const MigrationRuleDetailsFlyout: React.FC<MigrationRuleDetailsFlyoutProp
     const rule = useMemo(() => {
       if (isMigrationCustomRule(ruleMigration.elastic_rule)) {
         return convertMigrationCustomRuleToSecurityRulePayload(
-          ruleMigration.elastic_rule
+          ruleMigration.elastic_rule,
+          false
         ) as RuleResponse; // TODO: we need to adjust RuleOverviewTab to allow partial RuleResponse as a parameter;
       }
       return matchedPrebuiltRule;
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx
index a58681b6e771f..8f32308ed52c4 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/bulk_actions.tsx
@@ -6,7 +6,13 @@
  */
 
 import React from 'react';
-import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
+import {
+  EuiButton,
+  EuiButtonEmpty,
+  EuiFlexGroup,
+  EuiFlexItem,
+  EuiLoadingSpinner,
+} from '@elastic/eui';
 import * as i18n from './translations';
 
 export interface BulkActionsProps {
@@ -29,13 +35,14 @@ export const BulkActions: React.FC<BulkActionsProps> = React.memo(
     installSelectedRule,
   }) => {
     const disableInstallTranslatedRulesButton = isTableLoading || !numberOfTranslatedRules;
-    const showInstallSelectedRulesButton =
-      disableInstallTranslatedRulesButton && numberOfSelectedRules > 0;
+    const showInstallSelectedRulesButton = isTableLoading || numberOfSelectedRules > 0;
     return (
       <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
         {showInstallSelectedRulesButton ? (
           <EuiFlexItem grow={false}>
-            <EuiButton
+            <EuiButtonEmpty
+              iconType="plusInCircle"
+              color={'primary'}
               onClick={installSelectedRule}
               disabled={isTableLoading}
               data-test-subj="installSelectedRulesButton"
@@ -43,7 +50,7 @@ export const BulkActions: React.FC<BulkActionsProps> = React.memo(
             >
               {i18n.INSTALL_SELECTED_RULES(numberOfSelectedRules)}
               {isTableLoading && <EuiLoadingSpinner size="s" />}
-            </EuiButton>
+            </EuiButtonEmpty>
           </EuiFlexItem>
         ) : null}
         <EuiFlexItem grow={false}>
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx
index 13b451c2918d8..106e7ba514d3f 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx
@@ -5,7 +5,7 @@
  * 2.0.
  */
 
-import type { CriteriaWithPagination } from '@elastic/eui';
+import type { CriteriaWithPagination, EuiTableSelectionType } from '@elastic/eui';
 import {
   EuiSkeletonLoading,
   EuiSkeletonTitle,
@@ -14,6 +14,7 @@ import {
   EuiFlexItem,
   EuiSpacer,
   EuiBasicTable,
+  EuiButton,
 } from '@elastic/eui';
 import React, { useCallback, useMemo, useState } from 'react';
 
@@ -30,8 +31,12 @@ import { useGetMigrationPrebuiltRules } from '../../logic/use_get_migration_preb
 import * as logicI18n from '../../logic/translations';
 import { BulkActions } from './bulk_actions';
 import { SearchField } from './search_field';
+import { SiemMigrationRuleTranslationResult } from '../../../../../common/siem_migrations/constants';
+import * as i18n from './translations';
 
 const DEFAULT_PAGE_SIZE = 10;
+const DEFAULT_SORT_FIELD = 'translation_result';
+const DEFAULT_SORT_DIRECTION = 'desc';
 
 export interface MigrationRulesTableProps {
   /**
@@ -49,6 +54,8 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
 
     const [pageIndex, setPageIndex] = useState(0);
     const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
+    const [sortField, setSortField] = useState<keyof RuleMigration>(DEFAULT_SORT_FIELD);
+    const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>(DEFAULT_SORT_DIRECTION);
     const [searchTerm, setSearchTerm] = useState<string | undefined>();
 
     const { data: translationStats, isLoading: isStatsLoading } =
@@ -64,10 +71,33 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
       migrationId,
       page: pageIndex,
       perPage: pageSize,
+      sortField,
+      sortDirection,
       searchTerm,
     });
 
     const [selectedRuleMigrations, setSelectedRuleMigrations] = useState<RuleMigration[]>([]);
+    const tableSelection: EuiTableSelectionType<RuleMigration> = useMemo(
+      () => ({
+        selectable: (item: RuleMigration) => {
+          return (
+            !item.elastic_rule?.id &&
+            item.translation_result === SiemMigrationRuleTranslationResult.FULL
+          );
+        },
+        selectableMessage: (selectable: boolean, item: RuleMigration) => {
+          if (selectable) {
+            return '';
+          }
+          return item.elastic_rule?.id
+            ? i18n.ALREADY_TRANSLATED_RULE_TOOLTIP
+            : i18n.NOT_FULLY_TRANSLATED_RULE_TOOLTIP;
+        },
+        onSelectionChange: setSelectedRuleMigrations,
+        selected: selectedRuleMigrations,
+      }),
+      [selectedRuleMigrations]
+    );
 
     const pagination = useMemo(() => {
       return {
@@ -77,11 +107,25 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
       };
     }, [pageIndex, pageSize, total]);
 
+    const sorting = useMemo(() => {
+      return {
+        sort: {
+          field: sortField,
+          direction: sortDirection,
+        },
+      };
+    }, [sortDirection, sortField]);
+
     const onTableChange = useCallback(({ page, sort }: CriteriaWithPagination<RuleMigration>) => {
       if (page) {
         setPageIndex(page.index);
         setPageSize(page.size);
       }
+      if (sort) {
+        const { field, direction } = sort;
+        setSortField(field);
+        setSortDirection(direction);
+      }
     }, []);
 
     const handleOnSearch = useCallback((value: string) => {
@@ -94,10 +138,10 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
 
     const [isTableLoading, setTableLoading] = useState(false);
     const installSingleRule = useCallback(
-      async (migrationRule: RuleMigration, enable?: boolean) => {
+      async (migrationRule: RuleMigration, enabled = false) => {
         setTableLoading(true);
         try {
-          await installMigrationRules([migrationRule.id]);
+          await installMigrationRules({ ids: [migrationRule.id], enabled });
         } catch (error) {
           addError(error, { title: logicI18n.INSTALL_MIGRATION_RULES_FAILURE });
         } finally {
@@ -107,6 +151,24 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
       [addError, installMigrationRules]
     );
 
+    const installSelectedRule = useCallback(
+      async (enabled = false) => {
+        setTableLoading(true);
+        try {
+          await installMigrationRules({
+            ids: selectedRuleMigrations.map((rule) => rule.id),
+            enabled,
+          });
+        } catch (error) {
+          addError(error, { title: logicI18n.INSTALL_MIGRATION_RULES_FAILURE });
+        } finally {
+          setTableLoading(false);
+          setSelectedRuleMigrations([]);
+        }
+      },
+      [addError, installMigrationRules, selectedRuleMigrations]
+    );
+
     const installTranslatedRules = useCallback(
       async (enable?: boolean) => {
         setTableLoading(true);
@@ -121,12 +183,45 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
       [addError, installTranslatedMigrationRules]
     );
 
+    const isLoading = isStatsLoading || isPrebuiltRulesLoading || isDataLoading || isTableLoading;
+
     const ruleActionsFactory = useCallback(
       (ruleMigration: RuleMigration, closeRulePreview: () => void) => {
-        // TODO: Add flyout action buttons
-        return null;
+        const canMigrationRuleBeInstalled =
+          !isLoading &&
+          !ruleMigration.elastic_rule?.id &&
+          ruleMigration.translation_result === SiemMigrationRuleTranslationResult.FULL;
+        return (
+          <EuiFlexGroup>
+            <EuiFlexItem>
+              <EuiButton
+                disabled={!canMigrationRuleBeInstalled}
+                onClick={() => {
+                  installSingleRule(ruleMigration);
+                  closeRulePreview();
+                }}
+                data-test-subj="installMigrationRuleFromFlyoutButton"
+              >
+                {i18n.INSTALL_WITHOUT_ENABLING_BUTTON_LABEL}
+              </EuiButton>
+            </EuiFlexItem>
+            <EuiFlexItem>
+              <EuiButton
+                disabled={!canMigrationRuleBeInstalled}
+                onClick={() => {
+                  installSingleRule(ruleMigration, true);
+                  closeRulePreview();
+                }}
+                fill
+                data-test-subj="installAndEnableMigrationRuleFromFlyoutButton"
+              >
+                {i18n.INSTALL_AND_ENABLE_BUTTON_LABEL}
+              </EuiButton>
+            </EuiFlexItem>
+          </EuiFlexGroup>
+        );
       },
-      []
+      [installSingleRule, isLoading]
     );
 
     const {
@@ -143,8 +238,6 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
       installMigrationRule: installSingleRule,
     });
 
-    const isLoading = isStatsLoading || isPrebuiltRulesLoading || isDataLoading || isTableLoading;
-
     return (
       <>
         <EuiSkeletonLoading
@@ -168,8 +261,9 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
                     <BulkActions
                       isTableLoading={isLoading}
                       numberOfTranslatedRules={translationStats?.rules.installable ?? 0}
-                      numberOfSelectedRules={0}
+                      numberOfSelectedRules={selectedRuleMigrations.length}
                       installTranslatedRule={installTranslatedRules}
+                      installSelectedRule={installSelectedRule}
                     />
                   </EuiFlexItem>
                 </EuiFlexGroup>
@@ -178,12 +272,9 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
                   loading={isTableLoading}
                   items={ruleMigrations}
                   pagination={pagination}
+                  sorting={sorting}
                   onChange={onTableChange}
-                  selection={{
-                    selectable: () => true,
-                    onSelectionChange: setSelectedRuleMigrations,
-                    initialSelected: selectedRuleMigrations,
-                  }}
+                  selection={tableSelection}
                   itemId={'id'}
                   data-test-subj={'rules-translation-table'}
                   columns={rulesColumns}
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts
index 6803deb895d9b..79b5a1fe00900 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/translations.ts
@@ -80,3 +80,31 @@ export const INSTALL_TRANSLATED_ARIA_LABEL = i18n.translate(
     defaultMessage: 'Install all translated rules',
   }
 );
+
+export const ALREADY_TRANSLATED_RULE_TOOLTIP = i18n.translate(
+  'xpack.securitySolution.siemMigrations.rules.table.alreadyTranslatedTooltip',
+  {
+    defaultMessage: 'Already translated migration rule',
+  }
+);
+
+export const NOT_FULLY_TRANSLATED_RULE_TOOLTIP = i18n.translate(
+  'xpack.securitySolution.siemMigrations.rules.table.notFullyTranslatedTooltip',
+  {
+    defaultMessage: 'Not fully translated migration rule',
+  }
+);
+
+export const INSTALL_WITHOUT_ENABLING_BUTTON_LABEL = i18n.translate(
+  'xpack.securitySolution.siemMigrations.rules.table.installWithoutEnablingButtonLabel',
+  {
+    defaultMessage: 'Install without enabling',
+  }
+);
+
+export const INSTALL_AND_ENABLE_BUTTON_LABEL = i18n.translate(
+  'xpack.securitySolution.siemMigrations.rules.table.installAndEnableButtonLabel',
+  {
+    defaultMessage: 'Install and enable',
+  }
+);
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx
new file mode 100644
index 0000000000000..23980f5612f89
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/author.tsx
@@ -0,0 +1,39 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
+import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen';
+import * as i18n from './translations';
+import type { TableColumn } from './constants';
+
+const Author = ({ isPrebuiltRule }: { isPrebuiltRule: boolean }) => {
+  return (
+    <EuiFlexGroup gutterSize="s" alignItems="center">
+      {isPrebuiltRule && (
+        <EuiFlexItem grow={false}>
+          <EuiIcon type="logoElastic" size="m" />
+        </EuiFlexItem>
+      )}
+      <EuiFlexItem grow={false}>
+        {isPrebuiltRule ? i18n.ELASTIC_AUTHOR_TITLE : i18n.CUSTOM_AUTHOR_TITLE}
+      </EuiFlexItem>
+    </EuiFlexGroup>
+  );
+};
+
+export const createAuthorColumn = (): TableColumn => {
+  return {
+    field: 'elastic_rule.prebuilt_rule_id',
+    name: i18n.COLUMN_AUTHOR,
+    render: (_, rule: RuleMigration) => {
+      return <Author isPrebuiltRule={!!rule.elastic_rule?.prebuilt_rule_id} />;
+    },
+    sortable: true,
+    width: '10%',
+  };
+};
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx
index 4220a35ed4622..61af73ca7b9f8 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/index.tsx
@@ -8,6 +8,7 @@
 export * from './constants';
 
 export * from './actions';
+export * from './author';
 export * from './name';
 export * from './risk_score';
 export * from './severity';
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx
index 085a2f5c6a254..dd77636521eda 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx
@@ -12,12 +12,11 @@ import * as i18n from './translations';
 import type { TableColumn } from './constants';
 
 interface NameProps {
-  name: string;
   rule: RuleMigration;
   openMigrationRuleDetails: (rule: RuleMigration) => void;
 }
 
-const Name = ({ name, rule, openMigrationRuleDetails }: NameProps) => {
+const Name = ({ rule, openMigrationRuleDetails }: NameProps) => {
   return (
     <EuiLink
       onClick={() => {
@@ -25,7 +24,7 @@ const Name = ({ name, rule, openMigrationRuleDetails }: NameProps) => {
       }}
       data-test-subj="ruleName"
     >
-      {name}
+      {rule.elastic_rule?.title}
     </EuiLink>
   );
 };
@@ -36,10 +35,10 @@ export const createNameColumn = ({
   openMigrationRuleDetails: (rule: RuleMigration) => void;
 }): TableColumn => {
   return {
-    field: 'original_rule.title',
+    field: 'elastic_rule.title',
     name: i18n.COLUMN_NAME,
-    render: (value: RuleMigration['original_rule']['title'], rule: RuleMigration) => (
-      <Name name={value} rule={rule} openMigrationRuleDetails={openMigrationRuleDetails} />
+    render: (_, rule: RuleMigration) => (
+      <Name rule={rule} openMigrationRuleDetails={openMigrationRuleDetails} />
     ),
     sortable: true,
     truncateText: true,
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx
index e9eca65736a51..0fb78ae8bf709 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/risk_score.tsx
@@ -22,6 +22,6 @@ export const createRiskScoreColumn = (): TableColumn => {
     ),
     sortable: true,
     truncateText: true,
-    width: '75px',
+    width: '10%',
   };
 };
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx
index 4ea737844ad53..9a6c0b98ff317 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/severity.tsx
@@ -8,9 +8,7 @@
 import React from 'react';
 import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types';
 import { DEFAULT_TRANSLATION_SEVERITY } from '../../../../../common/siem_migrations/constants';
-import { getNormalizedSeverity } from '../../../../detection_engine/rule_management_ui/components/rules_table/helpers';
 import { SeverityBadge } from '../../../../common/components/severity_badge';
-import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen';
 import type { TableColumn } from './constants';
 import * as i18n from './translations';
 
@@ -19,8 +17,7 @@ export const createSeverityColumn = (): TableColumn => {
     field: 'elastic_rule.severity',
     name: i18n.COLUMN_SEVERITY,
     render: (value?: Severity) => <SeverityBadge value={value ?? DEFAULT_TRANSLATION_SEVERITY} />,
-    sortable: ({ elastic_rule: elasticRule }: RuleMigration) =>
-      getNormalizedSeverity((elasticRule?.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY),
+    sortable: true,
     truncateText: true,
     width: '12%',
   };
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx
index 982f6ba7580b2..5daec1f1b4fa9 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/status.tsx
@@ -18,8 +18,8 @@ export const createStatusColumn = (): TableColumn => {
     render: (value: RuleMigration['translation_result'], rule: RuleMigration) => (
       <StatusBadge value={value} installedRuleId={rule.elastic_rule?.id} />
     ),
-    sortable: false,
+    sortable: true,
     truncateText: true,
-    width: '12%',
+    width: '15%',
   };
 };
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts
index 5b40abd3d7485..64e459a609143 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/translations.ts
@@ -14,6 +14,27 @@ export const COLUMN_ACTIONS = i18n.translate(
   }
 );
 
+export const COLUMN_AUTHOR = i18n.translate(
+  'xpack.securitySolution.siemMigrations.rules.tableColumn.authorLabel',
+  {
+    defaultMessage: 'Author',
+  }
+);
+
+export const ELASTIC_AUTHOR_TITLE = i18n.translate(
+  'xpack.securitySolution.siemMigrations.rules.tableColumn.elasticAuthorTitle',
+  {
+    defaultMessage: 'Elastic',
+  }
+);
+
+export const CUSTOM_AUTHOR_TITLE = i18n.translate(
+  'xpack.securitySolution.siemMigrations.rules.tableColumn.customAuthorTitle',
+  {
+    defaultMessage: 'Custom',
+  }
+);
+
 export const ACTIONS_VIEW_LABEL = i18n.translate(
   'xpack.securitySolution.siemMigrations.rules.tableColumn.actionsViewLabel',
   {
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx
deleted file mode 100644
index aaf256cfb60b5..0000000000000
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.test.tsx
+++ /dev/null
@@ -1,19 +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 { shallow } from 'enzyme';
-
-import { StatusBadge } from '.';
-
-describe('StatusBadge', () => {
-  it('renders correctly', () => {
-    const wrapper = shallow(<StatusBadge value="full" />);
-
-    expect(wrapper.find('HealthTruncateText')).toHaveLength(1);
-  });
-});
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx
index 60f0ed94862ca..8f8bcff40f674 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx
@@ -8,9 +8,16 @@
 import React from 'react';
 import { euiLightVars } from '@kbn/ui-theme';
 
+import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiIcon, EuiToolTip } from '@elastic/eui';
+import { css } from '@emotion/css';
 import type { RuleMigrationTranslationResult } from '../../../../../common/siem_migrations/model/rule_migration.gen';
-import { HealthTruncateText } from '../../../../common/components/health_truncate_text';
 import { convertTranslationResultIntoText } from '../../utils/helpers';
+import * as i18n from './translations';
+
+const statusTextWrapperClassName = css`
+  width: 100%;
+  display: inline-grid;
+`;
 
 const { euiColorVis0, euiColorVis7, euiColorVis9 } = euiLightVars;
 const statusToColorMap: Record<RuleMigrationTranslationResult, string> = {
@@ -28,17 +35,28 @@ interface StatusBadgeProps {
 export const StatusBadge: React.FC<StatusBadgeProps> = React.memo(
   ({ value, installedRuleId, 'data-test-subj': dataTestSubj = 'translation-result' }) => {
     const translationResult = installedRuleId ? 'full' : value ?? 'untranslatable';
-    const displayValue = convertTranslationResultIntoText(translationResult);
+    const displayValue = installedRuleId
+      ? i18n.RULE_STATUS_INSTALLED
+      : convertTranslationResultIntoText(translationResult);
     const color = statusToColorMap[translationResult];
 
     return (
-      <HealthTruncateText
-        healthColor={color}
-        tooltipContent={displayValue}
-        dataTestSubj={dataTestSubj}
-      >
-        {displayValue}
-      </HealthTruncateText>
+      <EuiToolTip content={displayValue}>
+        {installedRuleId ? (
+          <EuiFlexGroup gutterSize="xs" alignItems="center">
+            <EuiFlexItem grow={false}>
+              <EuiIcon type={'check'} color={statusToColorMap.full} />
+            </EuiFlexItem>
+            <EuiFlexItem grow={false}>{displayValue}</EuiFlexItem>
+          </EuiFlexGroup>
+        ) : (
+          <EuiHealth color={color} data-test-subj={dataTestSubj}>
+            <div className={statusTextWrapperClassName}>
+              <span className="eui-textTruncate">{displayValue}</span>
+            </div>
+          </EuiHealth>
+        )}
+      </EuiToolTip>
     );
   }
 );
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.ts
new file mode 100644
index 0000000000000..0a7b1c37f7acf
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/translations.ts
@@ -0,0 +1,15 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+export const RULE_STATUS_INSTALLED = i18n.translate(
+  'xpack.securitySolution.siemMigrations.rules.status.installedLabel',
+  {
+    defaultMessage: 'Installed',
+  }
+);
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx
index b8b37bccaffd3..3c2d5a696ccc1 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx
@@ -10,6 +10,7 @@ import type { RuleMigration } from '../../../../common/siem_migrations/model/rul
 import type { TableColumn } from '../components/rules_table_columns';
 import {
   createActionsColumn,
+  createAuthorColumn,
   createNameColumn,
   createRiskScoreColumn,
   createSeverityColumn,
@@ -33,6 +34,7 @@ export const useMigrationRulesTableColumns = ({
       createStatusColumn(),
       createRiskScoreColumn(),
       createSeverityColumn(),
+      createAuthorColumn(),
       createActionsColumn({
         disableActions,
         openMigrationRuleDetails,
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts
index 5f59ceb9f76c2..4109575459233 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts
@@ -18,6 +18,8 @@ export const useGetMigrationRules = (params: {
   migrationId: string;
   page?: number;
   perPage?: number;
+  sortField: string;
+  sortDirection: 'asc' | 'desc';
   searchTerm?: string;
 }) => {
   const { addError } = useAppToasts();
diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts
index 755faa03bff14..2b28b3b944990 100644
--- a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts
+++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts
@@ -23,8 +23,8 @@ export const useInstallMigrationRules = (migrationId: string) => {
   const invalidateGetMigrationTranslationStats =
     useInvalidateGetMigrationTranslationStats(migrationId);
 
-  return useMutation<InstallMigrationRulesResponse, Error, string[]>(
-    (ids: string[]) => installMigrationRules({ migrationId, ids }),
+  return useMutation<InstallMigrationRulesResponse, Error, { ids: string[]; enabled: boolean }>(
+    ({ ids, enabled = false }) => installMigrationRules({ migrationId, ids, enabled }),
     {
       mutationKey: INSTALL_MIGRATION_RULES_MUTATION_KEY,
       onError: (error) => {
diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts
index dd13a75cdf83a..30037aeea88ae 100644
--- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts
+++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/get.ts
@@ -39,13 +39,20 @@ export const registerSiemRuleMigrationsGetRoute = (
       },
       withLicense(async (context, req, res): Promise<IKibanaResponse<GetRuleMigrationResponse>> => {
         const { migration_id: migrationId } = req.params;
-        const { page, per_page: perPage, search_term: searchTerm } = req.query;
+        const {
+          page,
+          per_page: perPage,
+          sort_field: sortField,
+          sort_direction: sortDirection,
+          search_term: searchTerm,
+        } = req.query;
         try {
           const ctx = await context.resolve(['securitySolution']);
           const ruleMigrationsClient = ctx.securitySolution.getSiemRuleMigrationsClient();
 
           const options: RuleMigrationGetOptions = {
             filters: { searchTerm },
+            sort: { sortField, sortDirection },
             size: perPage,
             from: page && perPage ? page * perPage : 0,
           };
diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts
index 7b41ea536aadf..9fae2922b486f 100644
--- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts
+++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install.ts
@@ -40,7 +40,7 @@ export const registerSiemRuleMigrationsInstallRoute = (
       withLicense(
         async (context, req, res): Promise<IKibanaResponse<InstallMigrationRulesResponse>> => {
           const { migration_id: migrationId } = req.params;
-          const ids = req.body;
+          const { ids, enabled = false } = req.body;
 
           try {
             const ctx = await context.resolve(['core', 'alerting', 'securitySolution']);
@@ -52,6 +52,7 @@ export const registerSiemRuleMigrationsInstallRoute = (
             await installTranslated({
               migrationId,
               ids,
+              enabled,
               securitySolutionContext,
               savedObjectsClient,
               rulesClient,
diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts
index ac6a598c4b92f..2cf2a2e2c8efd 100644
--- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts
+++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/install_translated.ts
@@ -50,6 +50,7 @@ export const registerSiemRuleMigrationsInstallTranslatedRoute = (
 
             await installTranslated({
               migrationId,
+              enabled: false,
               securitySolutionContext,
               savedObjectsClient,
               rulesClient,
diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts
index d74619e4c1251..8716c83ce6ba3 100644
--- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts
+++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts
@@ -27,6 +27,7 @@ import {
 
 const installPrebuiltRules = async (
   rulesToInstall: StoredRuleMigration[],
+  enabled: boolean,
   securitySolutionContext: SecuritySolutionApiRequestHandlerContext,
   rulesClient: RulesClient,
   savedObjectsClient: SavedObjectsClientContract,
@@ -41,7 +42,7 @@ const installPrebuiltRules = async (
       if (item.current) {
         acc.installed.push(item.current);
       } else {
-        acc.installable.push(item.target);
+        acc.installable.push({ ...item.target, enabled });
       }
       return acc;
     },
@@ -85,6 +86,7 @@ const installPrebuiltRules = async (
 
 export const installCustomRules = async (
   rulesToInstall: StoredRuleMigration[],
+  enabled: boolean,
   detectionRulesClient: IDetectionRulesClient,
   logger: Logger
 ): Promise<UpdateRuleMigrationInput[]> => {
@@ -96,8 +98,11 @@ export const installCustomRules = async (
       if (!isMigrationCustomRule(rule.elastic_rule)) {
         return;
       }
-      const payloadRule = convertMigrationCustomRuleToSecurityRulePayload(rule.elastic_rule);
-      const createdRule = await detectionRulesClient.createPrebuiltRule({
+      const payloadRule = convertMigrationCustomRuleToSecurityRulePayload(
+        rule.elastic_rule,
+        enabled
+      );
+      const createdRule = await detectionRulesClient.createCustomRule({
         params: payloadRule,
       });
       rulesToUpdate.push({
@@ -131,6 +136,11 @@ interface InstallTranslatedProps {
    */
   ids?: string[];
 
+  /**
+   * Indicates whether the installed migration rules should be enabled
+   */
+  enabled: boolean;
+
   /**
    * The security solution context
    */
@@ -155,6 +165,7 @@ interface InstallTranslatedProps {
 export const installTranslated = async ({
   migrationId,
   ids,
+  enabled,
   securitySolutionContext,
   rulesClient,
   savedObjectsClient,
@@ -186,6 +197,7 @@ export const installTranslated = async ({
 
   const updatedPrebuiltRules = await installPrebuiltRules(
     prebuiltRulesToInstall,
+    enabled,
     securitySolutionContext,
     rulesClient,
     savedObjectsClient,
@@ -194,6 +206,7 @@ export const installTranslated = async ({
 
   const updatedCustomRules = await installCustomRules(
     customRulesToInstall,
+    enabled,
     detectionRulesClient,
     logger
   );
diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts
index f11b24e50b95a..1eeb3ced0572a 100644
--- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts
+++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_rules_client.ts
@@ -15,10 +15,7 @@ import type {
   QueryDslQueryContainer,
 } from '@elastic/elasticsearch/lib/api/types';
 import type { StoredRuleMigration } from '../types';
-import {
-  SiemMigrationRuleTranslationResult,
-  SiemMigrationStatus,
-} from '../../../../../common/siem_migrations/constants';
+import { SiemMigrationStatus } from '../../../../../common/siem_migrations/constants';
 import type {
   ElasticRule,
   RuleMigration,
@@ -26,6 +23,8 @@ import type {
   RuleMigrationTranslationStats,
 } from '../../../../../common/siem_migrations/model/rule_migration.gen';
 import { RuleMigrationsDataBaseClient } from './rule_migrations_data_base_client';
+import { getSortingOptions, type RuleMigrationSort } from './sort';
+import { conditions as searchConditions } from './search';
 
 export type CreateRuleMigrationInput = Omit<
   RuleMigration,
@@ -47,6 +46,7 @@ export interface RuleMigrationFilters {
 }
 export interface RuleMigrationGetOptions {
   filters?: RuleMigrationFilters;
+  sort?: RuleMigrationSort;
   from?: number;
   size?: number;
 }
@@ -120,13 +120,19 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient
   /** Retrieves an array of rule documents of a specific migrations */
   async get(
     migrationId: string,
-    { filters = {}, from, size }: RuleMigrationGetOptions = {}
+    { filters = {}, sort = {}, from, size }: RuleMigrationGetOptions = {}
   ): Promise<{ total: number; data: StoredRuleMigration[] }> {
     const index = await this.getIndexName();
     const query = this.getFilterQuery(migrationId, { ...filters });
 
     const result = await this.esClient
-      .search<RuleMigration>({ index, query, sort: '_doc', from, size })
+      .search<RuleMigration>({
+        index,
+        query,
+        sort: sort.sortField ? getSortingOptions(sort) : undefined,
+        from,
+        size,
+      })
       .catch((error) => {
         this.logger.error(`Error searching rule migrations: ${error.message}`);
         throw error;
@@ -238,8 +244,8 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient
     const query = this.getFilterQuery(migrationId);
 
     const aggregations = {
-      prebuilt: { filter: conditions.isPrebuilt() },
-      installable: { filter: { bool: { must: conditions.isInstallable() } } },
+      prebuilt: { filter: searchConditions.isPrebuilt() },
+      installable: { filter: { bool: { must: searchConditions.isInstallable() } } },
     };
     const result = await this.esClient
       .search({ index, query, aggregations, _source: false })
@@ -351,47 +357,14 @@ export class RuleMigrationsDataRulesClient extends RuleMigrationsDataBaseClient
       filter.push({ terms: { _id: ids } });
     }
     if (installable) {
-      filter.push(...conditions.isInstallable());
+      filter.push(...searchConditions.isInstallable());
     }
     if (prebuilt) {
-      filter.push(conditions.isPrebuilt());
+      filter.push(searchConditions.isPrebuilt());
     }
     if (searchTerm?.length) {
-      filter.push(conditions.matchTitle(searchTerm));
+      filter.push(searchConditions.matchTitle(searchTerm));
     }
     return { bool: { filter } };
   }
 }
-
-const conditions = {
-  isFullyTranslated(): QueryDslQueryContainer {
-    return { term: { translation_result: SiemMigrationRuleTranslationResult.FULL } };
-  },
-  isNotInstalled(): QueryDslQueryContainer {
-    return {
-      nested: {
-        path: 'elastic_rule',
-        query: { bool: { must_not: { exists: { field: 'elastic_rule.id' } } } },
-      },
-    };
-  },
-  isPrebuilt(): QueryDslQueryContainer {
-    return {
-      nested: {
-        path: 'elastic_rule',
-        query: { exists: { field: 'elastic_rule.prebuilt_rule_id' } },
-      },
-    };
-  },
-  matchTitle(title: string): QueryDslQueryContainer {
-    return {
-      nested: {
-        path: 'elastic_rule',
-        query: { match: { 'elastic_rule.title': title } },
-      },
-    };
-  },
-  isInstallable(): QueryDslQueryContainer[] {
-    return [this.isFullyTranslated(), this.isNotInstalled()];
-  },
-};
diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts
index 7aca804c12890..952663c36123c 100644
--- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts
+++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts
@@ -19,14 +19,14 @@ export const ruleMigrationsFieldMap: FieldMap<SchemaFieldMapKeys<Omit<RuleMigrat
   original_rule: { type: 'nested', required: true },
   'original_rule.vendor': { type: 'keyword', required: true },
   'original_rule.id': { type: 'keyword', required: true },
-  'original_rule.title': { type: 'text', required: true },
+  'original_rule.title': { type: 'text', required: true, fields: { keyword: { type: 'keyword' } } },
   'original_rule.description': { type: 'text', required: false },
   'original_rule.query': { type: 'text', required: true },
   'original_rule.query_language': { type: 'keyword', required: true },
   'original_rule.annotations': { type: 'nested', required: false },
   'original_rule.annotations.mitre_attack': { type: 'keyword', array: true, required: false },
   elastic_rule: { type: 'nested', required: false },
-  'elastic_rule.title': { type: 'text', required: true },
+  'elastic_rule.title': { type: 'text', required: true, fields: { keyword: { type: 'keyword' } } },
   'elastic_rule.integration_ids': { type: 'keyword', array: true, required: false },
   'elastic_rule.query': { type: 'text', required: true },
   'elastic_rule.query_language': { type: 'keyword', required: true },
diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/search.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/search.ts
new file mode 100644
index 0000000000000..282f783671fdc
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/search.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
+import { SiemMigrationRuleTranslationResult } from '../../../../../common/siem_migrations/constants';
+
+export const conditions = {
+  isFullyTranslated(): QueryDslQueryContainer {
+    return { term: { translation_result: SiemMigrationRuleTranslationResult.FULL } };
+  },
+  isNotInstalled(): QueryDslQueryContainer {
+    return {
+      nested: {
+        path: 'elastic_rule',
+        query: { bool: { must_not: { exists: { field: 'elastic_rule.id' } } } },
+      },
+    };
+  },
+  isPrebuilt(): QueryDslQueryContainer {
+    return {
+      nested: {
+        path: 'elastic_rule',
+        query: { exists: { field: 'elastic_rule.prebuilt_rule_id' } },
+      },
+    };
+  },
+  matchTitle(title: string): QueryDslQueryContainer {
+    return {
+      nested: {
+        path: 'elastic_rule',
+        query: { match: { 'elastic_rule.title': title } },
+      },
+    };
+  },
+  isInstallable(): QueryDslQueryContainer[] {
+    return [this.isFullyTranslated(), this.isNotInstalled()];
+  },
+};
diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/sort.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/sort.ts
new file mode 100644
index 0000000000000..2d0ef644b8e56
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/sort.ts
@@ -0,0 +1,127 @@
+/*
+ * 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
+
+export interface RuleMigrationSort {
+  sortField?: string;
+  sortDirection?: estypes.SortOrder;
+}
+
+const sortMissingValue = (direction: estypes.SortOrder = 'asc') =>
+  direction === 'desc' ? '_last' : '_first';
+
+const sortingOptions = {
+  matchedPrebuiltRule(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] {
+    return [
+      {
+        'elastic_rule.prebuilt_rule_id': {
+          order: direction,
+          nested: { path: 'elastic_rule' },
+          missing: sortMissingValue(direction),
+        },
+      },
+    ];
+  },
+  severity(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] {
+    const field = 'elastic_rule.severity';
+    return [
+      {
+        _script: {
+          order: direction,
+          type: 'number',
+          script: {
+            source: `
+          if (doc.containsKey('${field}') && !doc['${field}'].empty) {
+            def value = doc['${field}'].value.toLowerCase();
+            if (value == 'critical') { return 3 }
+            if (value == 'high') { return 2 }
+            if (value == 'medium') { return 1 }
+            if (value == 'low') { return 0 }
+          }
+          return -1;
+          `,
+            lang: 'painless',
+          },
+          nested: { path: 'elastic_rule' },
+        },
+      },
+    ];
+  },
+  status(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] {
+    const field = 'translation_result';
+    const installedRuleField = 'elastic_rule.id';
+    return [
+      {
+        _script: {
+          order: direction,
+          type: 'number',
+          script: {
+            source: `
+          if (doc.containsKey('${field}') && !doc['${field}'].empty) {
+            def value = doc['${field}'].value.toLowerCase();
+            if (value == 'full') { return 2 }
+            if (value == 'partial') { return 1 }
+            if (value == 'untranslatable') { return 0 }
+          }
+          return -1;
+          `,
+            lang: 'painless',
+          },
+        },
+      },
+      {
+        _script: {
+          order: direction,
+          type: 'number',
+          script: {
+            source: `
+          if (doc.containsKey('${installedRuleField}') && !doc['${installedRuleField}'].empty) {
+            return 0;
+          }
+          return -1;
+          `,
+            lang: 'painless',
+          },
+          nested: { path: 'elastic_rule' },
+        },
+      },
+    ];
+  },
+  updated(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] {
+    return [{ updated_at: direction }];
+  },
+  name(direction: estypes.SortOrder = 'asc'): estypes.SortCombinations[] {
+    return [
+      { 'elastic_rule.title.keyword': { order: direction, nested: { path: 'elastic_rule' } } },
+    ];
+  },
+};
+
+const DEFAULT_SORTING: estypes.Sort = [
+  ...sortingOptions.status('desc'),
+  ...sortingOptions.matchedPrebuiltRule('desc'),
+  ...sortingOptions.severity(),
+  ...sortingOptions.updated(),
+];
+
+const sortingOptionsMap: {
+  [key: string]: (direction?: estypes.SortOrder) => estypes.SortCombinations[];
+} = {
+  'elastic_rule.title': sortingOptions.name,
+  'elastic_rule.severity': sortingOptions.severity,
+  'elastic_rule.prebuilt_rule_id': sortingOptions.matchedPrebuiltRule,
+  translation_result: sortingOptions.status,
+  updated_at: sortingOptions.updated,
+};
+
+export const getSortingOptions = (sort?: RuleMigrationSort): estypes.Sort => {
+  if (!sort?.sortField) {
+    return DEFAULT_SORTING;
+  }
+  return sortingOptionsMap[sort.sortField]?.(sort.sortDirection) ?? DEFAULT_SORTING;
+};