diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 07bc02dbceec3..132e0cb4051c1 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -7261,6 +7261,14 @@ "securitySolutionPackages.alertSuppressionRuleDetails.upsell": "La suppression d'alertes est configurée mais elle ne sera pas appliquée en raison d'une licence insuffisante", "securitySolutionPackages.alertSuppressionRuleForm.upsell": "La suppression d'alertes est activée avec la licence {requiredLicense} ou supérieure", "securitySolutionPackages.beta.label": "Bêta", + "securitySolutionPackages.csp.cspEvaluationBadge.failLabel": "Échec", + "securitySolutionPackages.csp.cspEvaluationBadge.naLabel": "S. O.", + "securitySolutionPackages.csp.cspEvaluationBadge.passLabel": "Réussite", + "securitySolutionPackages.csp.findings.findingsErrorToast.searchFailedTitle": "Échec de la recherche", + "securitySolutionPackages.csp.navigation.dashboardNavItemLabel": "Niveau de sécurité du cloud", + "securitySolutionPackages.csp.navigation.findingsNavItemLabel": "Résultats", + "securitySolutionPackages.csp.navigation.rulesNavItemLabel": "Règles", + "securitySolutionPackages.csp.navigation.vulnerabilityDashboardNavItemLabel": "Gestion des vulnérabilités natives du cloud", "securitySolutionPackages.dataTable.ariaLabel": "Alertes", "securitySolutionPackages.dataTable.columnHeaders.flyout.pane.removeColumnButtonLabel": "Supprimer la colonne", "securitySolutionPackages.dataTable.eventRenderedView.eventSummary.column": "Résumé des événements", @@ -7590,6 +7598,7 @@ "share.urlService.redirect.RedirectManager.missingParamLocator": "ID du localisateur non spécifié. Spécifiez le paramètre de recherche \"l\" dans l'URL ; ce devrait être un ID de localisateur existant.", "share.urlService.redirect.RedirectManager.missingParamParams": "Paramètres du localisateur non spécifiés. Spécifiez le paramètre de recherche \"p\" dans l'URL ; ce devrait être un objet sérialisé JSON des paramètres du localisateur.", "share.urlService.redirect.RedirectManager.missingParamVersion": "Version des paramètres du localisateur non spécifiée. Spécifiez le paramètre de recherche \"v\" dans l'URL ; ce devrait être la version de Kibana au moment de la génération des paramètres du localisateur.", + "sharedPlatformPackages.csp.common.utils.helpers.unknownError": "Erreur inconnue", "sharedUXPackages.buttonToolbar.buttons.addFromLibrary.libraryButtonLabel": "Ajouter depuis la bibliothèque", "sharedUXPackages.buttonToolbar.toolbar.errorToolbarText": "Il y a plus de 120 boutons supplémentaires. Nous vous invitons à limiter le nombre de boutons.", "sharedUXPackages.card.noData.description": "Utilisez Elastic Agent pour collecter de manière simple et unifiée les données de vos machines.", @@ -14597,7 +14606,6 @@ "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilities": "Vulnérabilités", "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilityCount": "Vulnérabilités", "xpack.csp.common.component.multiSelectFilter.searchWord": "Recherche", - "sharedPlatformPackages.csp.common.utils.helpers.unknownError": "Erreur inconnue", "xpack.csp.compactFormattedNumber.naTitle": "S. O.", "xpack.csp.complianceScoreBar.tooltipTitle": "{failed} échecs et {passed} réussites de résultats", "xpack.csp.complianceScoreChart.counterButtonLink.failedFindingsTooltip": "Échec des résultats", @@ -14612,9 +14620,6 @@ "xpack.csp.createPackagePolicy.customAssetsTab.rulesViewLabel": "Afficher les règles CSP", "xpack.csp.createPackagePolicy.customAssetsTab.vulnerabilityDashboardViewLabel": "Afficher le tableau de bord CNVM", "xpack.csp.createPackagePolicy.customAssetsTab.vulnerabilityFindingsViewLabel": "Afficher les résultats des vulnérabilités", - "securitySolutionPackages.csp.cspEvaluationBadge.failLabel": "Échec", - "securitySolutionPackages.csp.cspEvaluationBadge.naLabel": "S. O.", - "securitySolutionPackages.csp.cspEvaluationBadge.passLabel": "Réussite", "xpack.csp.cspIntegration.gcpCloudCredentials.cloudFormationSupportedMessage": "La fonctionnalité Lancer Cloud Shell pour obtenir les informations d'identification de façon automatisée n’est pas pris en charge dans la version d'intégration actuelle. Veuillez effectuer une mise à niveau vers la dernière version pour activer Lancer Cloud Shell pour les informations d'identification automatisées.", "xpack.csp.cspmIntegration.awsOption.benchmarkTitle": "CIS AWS", "xpack.csp.cspmIntegration.awsOption.nameTitle": "AWS", @@ -14698,7 +14703,6 @@ "xpack.csp.findings.distributionBar.totalPassedLabel": "Réussite des résultats", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "Une erreur s’est produite lors de la récupération des résultats de recherche.", "xpack.csp.findings.errorCallout.showErrorButtonLabel": "Afficher le message d'erreur", - "securitySolutionPackages.csp.findings.findingsErrorToast.searchFailedTitle": "Échec de la recherche", "xpack.csp.findings.findingsFlyout.calloutTitle": "Certains champs ne sont pas fournis par {vendor}", "xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceId": "ID ressource", "xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceName": "Nom de ressource", @@ -14868,10 +14872,6 @@ "xpack.csp.kspmIntegration.integration.shortNameTitle": "KSPM", "xpack.csp.kspmIntegration.vanillaOption.benchmarkTitle": "CIS Kubernetes", "xpack.csp.kspmIntegration.vanillaOption.nameTitle": "Autogéré", - "securitySolutionPackages.csp.navigation.dashboardNavItemLabel": "Niveau de sécurité du cloud", - "securitySolutionPackages.csp.navigation.findingsNavItemLabel": "Résultats", - "securitySolutionPackages.csp.navigation.rulesNavItemLabel": "Règles", - "securitySolutionPackages.csp.navigation.vulnerabilityDashboardNavItemLabel": "Gestion des vulnérabilités natives du cloud", "xpack.csp.noFindingsStates.indexing.indexingButtonTitle": "Évaluation du niveau en cours", "xpack.csp.noFindingsStates.indexing.indexingDescription": "En attente de la collecte et de l'indexation des données. Revenez plus tard pour voir vos résultats", "xpack.csp.noFindingsStates.indexTimeout.indexTimeoutDescription": "La collecte des résultats prend plus de temps que prévu. {docs}.", @@ -37703,7 +37703,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.multiSelectFields.placeholderText": "Sélectionner un champ", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText": "Sélectionner un champ", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel": "Champs", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin": "Au moins un champ est requis.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError": "Le format de l’URL n’est pas valide.", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton": "Réinitialiser sur les modèles d'indexation par défaut", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.rulePreviewTitle": "Aperçu de la règle", @@ -39008,7 +39007,6 @@ "xpack.securitySolution.detectionEngine.userUnauthenticatedMsgBody": "Vous ne disposez pas des autorisations requises pour visualiser le moteur de détection. Pour une aide supplémentaire, contactez votre administrateur.", "xpack.securitySolution.detectionEngine.userUnauthenticatedTitle": "Autorisations de moteur de détection requises", "xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errMin": "La taille de la fenêtre d'historique doit être supérieure à 0.", - "xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsFieldsMax": "Le nombre de champs doit être de 3 au maximum.", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityFieldFieldData.thresholdCardinalityFieldNotSuppliedMessage": "Un champ Cardinalité est requis.", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityValueFieldData.numberGreaterThanOrEqualOneErrorMessage": "La valeur doit être supérieure ou égale à un.", "xpack.securitySolution.detectionEngine.validations.thresholdFieldFieldData.arrayLengthGreaterThanMaxErrorMessage": "Le nombre de champs doit être de 3 au maximum.", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 8641da40c1e3c..ceafc1e58d002 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -7139,6 +7139,14 @@ "securitySolutionPackages.alertSuppressionRuleDetails.upsell": "アラート非表示が構成されていますが、ライセンス不足のため適用されません", "securitySolutionPackages.alertSuppressionRuleForm.upsell": "アラートの非表示は、{requiredLicense}ライセンス以上で有効です", "securitySolutionPackages.beta.label": "ベータ", + "securitySolutionPackages.csp.cspEvaluationBadge.failLabel": "失敗", + "securitySolutionPackages.csp.cspEvaluationBadge.naLabel": "N/A", + "securitySolutionPackages.csp.cspEvaluationBadge.passLabel": "合格", + "securitySolutionPackages.csp.findings.findingsErrorToast.searchFailedTitle": "検索失敗", + "securitySolutionPackages.csp.navigation.dashboardNavItemLabel": "クラウドセキュリティ態勢", + "securitySolutionPackages.csp.navigation.findingsNavItemLabel": "調査結果", + "securitySolutionPackages.csp.navigation.rulesNavItemLabel": "ルール", + "securitySolutionPackages.csp.navigation.vulnerabilityDashboardNavItemLabel": "Cloud Native Vulnerability Management", "securitySolutionPackages.dataTable.ariaLabel": "アラート", "securitySolutionPackages.dataTable.columnHeaders.flyout.pane.removeColumnButtonLabel": "列を削除", "securitySolutionPackages.dataTable.eventRenderedView.eventSummary.column": "イベント概要", @@ -7467,6 +7475,7 @@ "share.urlService.redirect.RedirectManager.missingParamLocator": "ロケーターIDが指定されていません。URLで「l」検索パラメーターを指定します。これは既存のロケーターIDにしてください。", "share.urlService.redirect.RedirectManager.missingParamParams": "ロケーターパラメーターが指定されていません。URLで「p」検索パラメーターを指定します。これはロケーターパラメーターのJSONシリアル化オブジェクトにしてください。", "share.urlService.redirect.RedirectManager.missingParamVersion": "ロケーターパラメーターバージョンが指定されていません。URLで「v」検索パラメーターを指定します。これはロケーターパラメーターが生成されたときのKibanaのリリースバージョンです。", + "sharedPlatformPackages.csp.common.utils.helpers.unknownError": "不明なエラー", "sharedUXPackages.buttonToolbar.buttons.addFromLibrary.libraryButtonLabel": "ライブラリから追加", "sharedUXPackages.buttonToolbar.toolbar.errorToolbarText": "120以上のボタンがあります。ボタンの数を制限することを検討してください。", "sharedUXPackages.card.noData.description": "Elasticエージェントを使用すると、シンプルで統一された方法でコンピューターからデータを収集するできます。", @@ -14464,7 +14473,6 @@ "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilities": "脆弱性", "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilityCount": "脆弱性", "xpack.csp.common.component.multiSelectFilter.searchWord": "検索", - "sharedPlatformPackages.csp.common.utils.helpers.unknownError": "不明なエラー", "xpack.csp.compactFormattedNumber.naTitle": "N/A", "xpack.csp.complianceScoreBar.tooltipTitle": "{failed}が失敗し、{passed}が調査結果に合格しました", "xpack.csp.complianceScoreChart.counterButtonLink.failedFindingsTooltip": "失敗した調査結果", @@ -14479,9 +14487,6 @@ "xpack.csp.createPackagePolicy.customAssetsTab.rulesViewLabel": "CSPルールを表示", "xpack.csp.createPackagePolicy.customAssetsTab.vulnerabilityDashboardViewLabel": "CNVMダッシュボードを表示", "xpack.csp.createPackagePolicy.customAssetsTab.vulnerabilityFindingsViewLabel": "脆弱性の調査結果を表示", - "securitySolutionPackages.csp.cspEvaluationBadge.failLabel": "失敗", - "securitySolutionPackages.csp.cspEvaluationBadge.naLabel": "N/A", - "securitySolutionPackages.csp.cspEvaluationBadge.passLabel": "合格", "xpack.csp.cspIntegration.gcpCloudCredentials.cloudFormationSupportedMessage": "Launch Cloud ShellLaunch Cloud Formation for Automated Credentialsは、現在の統合バージョンではサポートされていません。Launch Cloud Shell for Automated Credentialsを有効化するには、最新バージョンにアップグレードしてください。", "xpack.csp.cspmIntegration.awsOption.benchmarkTitle": "CIS AWS", "xpack.csp.cspmIntegration.awsOption.nameTitle": "AWS", @@ -14564,7 +14569,6 @@ "xpack.csp.findings.distributionBar.totalPassedLabel": "合格した調査結果", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "検索結果の取得中にエラーが発生しました", "xpack.csp.findings.errorCallout.showErrorButtonLabel": "エラーメッセージを表示", - "securitySolutionPackages.csp.findings.findingsErrorToast.searchFailedTitle": "検索失敗", "xpack.csp.findings.findingsFlyout.calloutTitle": "一部のフィールドは{vendor}によって提供されていません", "xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceId": "リソースID", "xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceName": "リソース名", @@ -14733,10 +14737,6 @@ "xpack.csp.kspmIntegration.integration.shortNameTitle": "KSPM", "xpack.csp.kspmIntegration.vanillaOption.benchmarkTitle": "CIS Kubernetes", "xpack.csp.kspmIntegration.vanillaOption.nameTitle": "自己管理", - "securitySolutionPackages.csp.navigation.dashboardNavItemLabel": "クラウドセキュリティ態勢", - "securitySolutionPackages.csp.navigation.findingsNavItemLabel": "調査結果", - "securitySolutionPackages.csp.navigation.rulesNavItemLabel": "ルール", - "securitySolutionPackages.csp.navigation.vulnerabilityDashboardNavItemLabel": "Cloud Native Vulnerability Management", "xpack.csp.noFindingsStates.indexing.indexingButtonTitle": "態勢評価中", "xpack.csp.noFindingsStates.indexing.indexingDescription": "データの収集とインデックス作成を待機しています。結果を表示するには、しばらくたってから確認してください", "xpack.csp.noFindingsStates.indexTimeout.indexTimeoutDescription": "調査結果の収集に想定よりも時間がかかっています。{docs}。", @@ -37561,7 +37561,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.multiSelectFields.placeholderText": "フィールドを選択", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText": "フィールドを選択", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel": "フィールド", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin": "1つ以上のフィールドが必要です。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError": "URLの形式が無効です", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton": "デフォルトインデックスパターンにリセット", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.rulePreviewTitle": "ルールプレビュー", @@ -38865,7 +38864,6 @@ "xpack.securitySolution.detectionEngine.userUnauthenticatedMsgBody": "検出エンジンを表示するための必要なアクセス権がありません。ヘルプについては、管理者にお問い合わせください。", "xpack.securitySolution.detectionEngine.userUnauthenticatedTitle": "検出エンジンアクセス権が必要です", "xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errMin": "履歴ウィンドウサイズは0よりも大きい値でなければなりません。", - "xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsFieldsMax": "フィールド数は3以下でなければなりません。", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityFieldFieldData.thresholdCardinalityFieldNotSuppliedMessage": "カーディナリティフィールドは必須です。", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityValueFieldData.numberGreaterThanOrEqualOneErrorMessage": "値は 1 以上でなければなりません。", "xpack.securitySolution.detectionEngine.validations.thresholdFieldFieldData.arrayLengthGreaterThanMaxErrorMessage": "フィールド数は3以下でなければなりません。", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index d6bca0a79d647..6a3f3c2e483a2 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -7023,6 +7023,14 @@ "securitySolutionPackages.alertSuppressionRuleDetails.upsell": "已配置告警阻止,但由于许可不足而无法应用", "securitySolutionPackages.alertSuppressionRuleForm.upsell": "告警阻止通过{requiredLicense}或更高级许可证启用", "securitySolutionPackages.beta.label": "公测版", + "securitySolutionPackages.csp.cspEvaluationBadge.failLabel": "失败", + "securitySolutionPackages.csp.cspEvaluationBadge.naLabel": "不可用", + "securitySolutionPackages.csp.cspEvaluationBadge.passLabel": "通过", + "securitySolutionPackages.csp.findings.findingsErrorToast.searchFailedTitle": "搜索失败", + "securitySolutionPackages.csp.navigation.dashboardNavItemLabel": "云安全态势", + "securitySolutionPackages.csp.navigation.findingsNavItemLabel": "结果", + "securitySolutionPackages.csp.navigation.rulesNavItemLabel": "规则", + "securitySolutionPackages.csp.navigation.vulnerabilityDashboardNavItemLabel": "云原生漏洞管理", "securitySolutionPackages.dataTable.ariaLabel": "告警", "securitySolutionPackages.dataTable.columnHeaders.flyout.pane.removeColumnButtonLabel": "移除列", "securitySolutionPackages.dataTable.eventRenderedView.eventSummary.column": "事件摘要", @@ -7352,6 +7360,7 @@ "share.urlService.redirect.RedirectManager.missingParamLocator": "未指定定位器 ID。在 URL 中指定'l'搜索参数,其应为现有定位器 ID。", "share.urlService.redirect.RedirectManager.missingParamParams": "定位器参数未指定。在 URL 中指定'p'搜索参数,其应为定位器参数的 JSON 序列化对象。", "share.urlService.redirect.RedirectManager.missingParamVersion": "定位器参数版本未指定。在 URL 中指定'v'搜索参数,其应为生成定位器参数时 Kibana 的版本。", + "sharedPlatformPackages.csp.common.utils.helpers.unknownError": "未知错误", "sharedUXPackages.buttonToolbar.buttons.addFromLibrary.libraryButtonLabel": "从库中添加", "sharedUXPackages.buttonToolbar.toolbar.errorToolbarText": "有 120 多个附加按钮。请考虑限制按钮数量。", "sharedUXPackages.card.noData.description": "使用 Elastic 代理以简单统一的方式从您的计算机中收集数据。", @@ -14194,7 +14203,6 @@ "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilities": "漏洞", "xpack.csp.cnvmDashboardTable.section.topVulnerableResources.column.vulnerabilityCount": "漏洞", "xpack.csp.common.component.multiSelectFilter.searchWord": "搜索", - "sharedPlatformPackages.csp.common.utils.helpers.unknownError": "未知错误", "xpack.csp.compactFormattedNumber.naTitle": "不可用", "xpack.csp.complianceScoreBar.tooltipTitle": "{failed} 个失败和 {passed} 个通过的结果", "xpack.csp.complianceScoreChart.counterButtonLink.failedFindingsTooltip": "失败的结果", @@ -14209,9 +14217,6 @@ "xpack.csp.createPackagePolicy.customAssetsTab.rulesViewLabel": "查看 CSP 规则", "xpack.csp.createPackagePolicy.customAssetsTab.vulnerabilityDashboardViewLabel": "查看 CNVM 仪表板", "xpack.csp.createPackagePolicy.customAssetsTab.vulnerabilityFindingsViewLabel": "查看漏洞结果", - "securitySolutionPackages.csp.cspEvaluationBadge.failLabel": "失败", - "securitySolutionPackages.csp.cspEvaluationBadge.naLabel": "不可用", - "securitySolutionPackages.csp.cspEvaluationBadge.passLabel": "通过", "xpack.csp.cspIntegration.gcpCloudCredentials.cloudFormationSupportedMessage": "当前集成版本不支持为自动化凭据启动 Cloud Shell。请升级到最新版本以启用为自动化凭据启动 Cloud Shell。", "xpack.csp.cspmIntegration.awsOption.benchmarkTitle": "CIS AWS", "xpack.csp.cspmIntegration.awsOption.nameTitle": "AWS", @@ -14295,7 +14300,6 @@ "xpack.csp.findings.distributionBar.totalPassedLabel": "通过的结果", "xpack.csp.findings.errorCallout.pageSearchErrorTitle": "检索搜索结果时遇到问题", "xpack.csp.findings.errorCallout.showErrorButtonLabel": "显示错误消息", - "securitySolutionPackages.csp.findings.findingsErrorToast.searchFailedTitle": "搜索失败", "xpack.csp.findings.findingsFlyout.calloutTitle": "{vendor} 未提供某些字段", "xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceId": "资源 ID", "xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceName": "资源名称", @@ -14465,10 +14469,6 @@ "xpack.csp.kspmIntegration.integration.shortNameTitle": "KSPM", "xpack.csp.kspmIntegration.vanillaOption.benchmarkTitle": "CIS Kubernetes", "xpack.csp.kspmIntegration.vanillaOption.nameTitle": "自管型", - "securitySolutionPackages.csp.navigation.dashboardNavItemLabel": "云安全态势", - "securitySolutionPackages.csp.navigation.findingsNavItemLabel": "结果", - "securitySolutionPackages.csp.navigation.rulesNavItemLabel": "规则", - "securitySolutionPackages.csp.navigation.vulnerabilityDashboardNavItemLabel": "云原生漏洞管理", "xpack.csp.noFindingsStates.indexing.indexingButtonTitle": "正进行态势评估", "xpack.csp.noFindingsStates.indexing.indexingDescription": "正在等待要收集和索引的数据。请稍后返回检查以查看结果", "xpack.csp.noFindingsStates.indexTimeout.indexTimeoutDescription": "收集结果所需的时间长于预期。{docs}。", @@ -36993,7 +36993,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.multiSelectFields.placeholderText": "选择字段", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText": "选择字段", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel": "字段", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin": "至少需要一个字段。", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError": "URL 的格式无效", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton": "重置为默认索引模式", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.rulePreviewTitle": "规则预览", @@ -38292,7 +38291,6 @@ "xpack.securitySolution.detectionEngine.userUnauthenticatedMsgBody": "您没有所需的权限,无法查看检测引擎。若需要更多帮助,请联系您的管理员。", "xpack.securitySolution.detectionEngine.userUnauthenticatedTitle": "需要检测引擎权限", "xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errMin": "历史记录窗口大小必须大于 0。", - "xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsFieldsMax": "字段数目不得超过 3 个。", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityFieldFieldData.thresholdCardinalityFieldNotSuppliedMessage": "基数字段必填。", "xpack.securitySolution.detectionEngine.validations.thresholdCardinalityValueFieldData.numberGreaterThanOrEqualOneErrorMessage": "值必须大于或等于 1。", "xpack.securitySolution.detectionEngine.validations.thresholdFieldFieldData.arrayLengthGreaterThanMaxErrorMessage": "字段数目不得超过 3 个。", diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts index 139acb0473c8b..9b60c15dc1354 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/ml/hooks/use_ml_rule_config.ts @@ -5,16 +5,15 @@ * 2.0. */ -import { useMemo } from 'react'; import type { DataViewFieldBase } from '@kbn/es-query'; import type { FieldSpec } from '@kbn/data-plugin/common'; -import { getTermsAggregationFields } from '../../../../detection_engine/rule_creation_ui/components/step_define_rule/utils'; import { useRuleFields } from '../../../../detection_engine/rule_management/logic/use_rule_fields'; import { useMlCapabilities } from './use_ml_capabilities'; import { useMlRuleValidations } from './use_ml_rule_validations'; import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions'; import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license'; +import { useTermsAggregationFields } from '../../../hooks/use_terms_aggregation_fields'; export interface UseMlRuleConfigReturn { hasMlAdminPermissions: boolean; @@ -45,10 +44,7 @@ export const useMLRuleConfig = ({ const { loading: mlFieldsLoading, fields: mlFields } = useRuleFields({ machineLearningJobId, }); - const mlSuppressionFields = useMemo( - () => getTermsAggregationFields(mlFields as FieldSpec[]), - [mlFields] - ); + const mlSuppressionFields = useTermsAggregationFields(mlFields); return { hasMlAdminPermissions: hasMlAdminPermissions(mlCapabilities), diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/constants.ts b/x-pack/solutions/security/plugins/security_solution/public/common/constants.ts index c114f70915a75..a9761e87c20e1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/constants.ts @@ -18,3 +18,5 @@ export const RISK_SCORE_HIGH = 73; export const RISK_SCORE_CRITICAL = 99; export const ONBOARDING_VIDEO_SOURCE = '//play.vidyard.com/K6kKDBbP9SpXife9s2tHNP.html?'; + +export const DEFAULT_HISTORY_WINDOW_SIZE = '7d'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_terms_aggregation_fields.ts b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_terms_aggregation_fields.ts new file mode 100644 index 0000000000000..e62b16e826766 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_terms_aggregation_fields.ts @@ -0,0 +1,29 @@ +/* + * 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 { useMemo } from 'react'; +import type { FieldSpec } from '@kbn/data-views-plugin/common'; +import type { DataViewFieldBase } from '@kbn/es-query'; +import { getTermsAggregationFields } from '../../detection_engine/rule_creation_ui/components/step_define_rule/utils'; + +export function useTermsAggregationFields(fields?: DataViewFieldBase[]) { + const termsAggregationFields = useMemo( + /** + * Typecasting to FieldSpec because fields is + * typed as DataViewFieldBase[] which does not have + * the 'aggregatable' property, however the type is incorrect + * + * fields does contain elements with the aggregatable property. + * We will need to determine where these types are defined and + * figure out where the discrepancy is. + */ + () => getTermsAggregationFields((fields as FieldSpec[]) ?? []), + [fields] + ); + + return termsAggregationFields; +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/utils/date_math.ts b/x-pack/solutions/security/plugins/security_solution/public/common/utils/date_math.ts new file mode 100644 index 0000000000000..28dbe15955a27 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/common/utils/date_math.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/** + * Converts a date math string to a duration string by removing the 'now-' prefix. + * + * @param historyStart - Date math string to convert. For example, "now-30d". + * @returns Resulting duration string. For example, "30d". + */ +export const convertDateMathToDuration = (dateMathString: string): string => { + if (dateMathString.startsWith('now-')) { + return dateMathString.substring(4); + } + + return dateMathString; +}; + +/** + * Converts a duration string to a dateMath string by adding the 'now-' prefix. + * + * @param durationString - Duration string to convert. For example, "30d". + * @returns Resulting date math string. For example, "now-30d". + */ +export const convertDurationToDateMath = (durationString: string): string => + `now-${durationString}`; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/history_window_start_edit.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/history_window_start_edit.tsx new file mode 100644 index 0000000000000..c876fe926ce4f --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/history_window_start_edit.tsx @@ -0,0 +1,44 @@ +/* + * 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 { ScheduleItemField } from '../schedule_item_field'; +import { type FieldConfig, UseField } from '../../../../shared_imports'; +import { type HistoryWindowStart } from '../../../../../common/api/detection_engine'; +import * as i18n from './translations'; +import { validateHistoryWindowStart } from './validate_history_window_start'; + +const COMPONENT_PROPS = { + idAria: 'historyWindowSize', + dataTestSubj: 'historyWindowSize', + timeTypes: ['m', 'h', 'd'], +}; + +interface HistoryWindowStartEditProps { + path: string; +} + +export function HistoryWindowStartEdit({ path }: HistoryWindowStartEditProps): JSX.Element { + return ( + + ); +} + +const HISTORY_WINDOW_START_FIELD_CONFIG: FieldConfig = { + label: i18n.HISTORY_WINDOW_START_LABEL, + helpText: i18n.HELP_TEXT, + validations: [ + { + validator: validateHistoryWindowStart, + }, + ], +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/index.tsx similarity index 51% rename from x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/translations.ts rename to x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/index.tsx index 1bf73b4a46b48..a904f5d427710 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/index.tsx @@ -5,11 +5,4 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; - -export const NEW_TERMS_FIELD_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText', - { - defaultMessage: 'Select a field', - } -); +export { HistoryWindowStartEdit } from './history_window_start_edit'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/translations.ts new file mode 100644 index 0000000000000..75ff11fe5e1b6 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/translations.ts @@ -0,0 +1,36 @@ +/* + * 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 HISTORY_WINDOW_START_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.historyWindowSizeLabel', + { + defaultMessage: 'History Window Size', + } +); + +export const HELP_TEXT = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.historyWindowSizeHelpText', + { + defaultMessage: "New terms rules only alert if terms don't appear in historical data.", + } +); + +export const MUST_BE_POSITIVE_INTEGER_VALIDATION_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errNumber', + { + defaultMessage: 'History window size must be a positive number.', + } +); + +export const MUST_BE_GREATER_THAN_ZERO_VALIDATION_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errMin', + { + defaultMessage: 'History window size must be greater than 0.', + } +); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/validate_history_window_start.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/validate_history_window_start.ts new file mode 100644 index 0000000000000..4e274683ad08b --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/history_window_start_edit/validate_history_window_start.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { type ValidationFunc } from '../../../../shared_imports'; +import * as i18n from './translations'; + +export function validateHistoryWindowStart(...args: Parameters) { + const [{ path, value }] = args; + + const historyWindowSize = Number.parseInt(String(value), 10); + + if (Number.isNaN(historyWindowSize)) { + return { + code: 'ERR_NOT_INT_NUMBER', + path, + message: i18n.MUST_BE_POSITIVE_INTEGER_VALIDATION_ERROR, + }; + } + + if (historyWindowSize <= 0) { + return { + code: 'ERR_MIN_LENGTH', + path, + message: i18n.MUST_BE_GREATER_THAN_ZERO_VALIDATION_ERROR, + }; + } +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/index.tsx new file mode 100644 index 0000000000000..22c3c9e9047c0 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { NewTermsFieldsEdit } from './new_terms_fields_edit'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit.tsx new file mode 100644 index 0000000000000..f50c62ed8f7a0 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_edit.tsx @@ -0,0 +1,43 @@ +/* + * 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, { memo } from 'react'; +import { UseField, fieldValidators } from '../../../../shared_imports'; +import { NewTermsFieldsField } from './new_terms_fields_field'; +import * as i18n from './translations'; + +interface NewTermsFieldsEditProps { + path: string; + fieldNames: string[]; +} + +export const NewTermsFieldsEdit = memo(function NewTermsFieldsEdit({ + path, + fieldNames, +}: NewTermsFieldsEditProps): JSX.Element { + return ( + + ); +}); + +const NEW_TERMS_FIELDS_CONFIG = { + label: i18n.NEW_TERMS_FIELDS_LABEL, + helpText: i18n.HELP_TEXT, + validations: [ + { + validator: fieldValidators.emptyField(i18n.MIN_FIELDS_COUNT_VALIDATION_ERROR), + }, + { + validator: fieldValidators.maxLengthField(i18n.MAX_FIELDS_COUNT_VALIDATION_ERROR), + }, + ], +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_field.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_field.tsx new file mode 100644 index 0000000000000..a9c1148402f12 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/new_terms_fields_field.tsx @@ -0,0 +1,40 @@ +/* + * 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, { memo } from 'react'; + +import type { DataViewFieldBase } from '@kbn/es-query'; +import { ComboBoxField } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import type { FieldHook } from '../../../../shared_imports'; +import { PLACEHOLDER } from './translations'; + +interface NewTermsFieldsProps { + fieldNames: DataViewFieldBase[]; + field: FieldHook; +} + +const FIELD_COMBO_BOX_WIDTH = 410; + +const fieldDescribedByIds = 'newTermsFieldEdit'; + +export const NewTermsFieldsField = memo(function NewTermsFieldsField({ + fieldNames, + field, +}: NewTermsFieldsProps): JSX.Element { + const fieldEuiFieldProps = { + fullWidth: true, + noSuggestions: false, + options: fieldNames.map((name) => ({ label: name })), + placeholder: PLACEHOLDER, + onCreateOption: undefined, + style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, + }; + + return ( + + ); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/translations.ts new file mode 100644 index 0000000000000..4eb18b9a318e0 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/new_terms_fields_edit/translations.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../../common/constants'; + +export const NEW_TERMS_FIELDS_LABEL = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel', + { + defaultMessage: 'Fields', + } +); + +export const PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.placeholderText', + { + defaultMessage: 'Select a field', + } +); + +export const HELP_TEXT = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNewTermsFieldHelpText', + { + defaultMessage: 'Select a field to check for new terms.', + } +); + +export const MIN_FIELDS_COUNT_VALIDATION_ERROR = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsField.minFieldsCountError', + { + defaultMessage: 'A minimum of one field is required.', + } +); + +export const MAX_FIELDS_COUNT_VALIDATION_ERROR = { + length: MAX_NUMBER_OF_NEW_TERMS_FIELDS, + message: i18n.translate( + 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsField.maxFieldsCountError', + { + defaultMessage: 'Number of fields must be 3 or less.', + } + ), +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/index.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/index.ts new file mode 100644 index 0000000000000..f2458bbb5a4e1 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { ScheduleItemField } from './schedule_item_field'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.test.tsx new file mode 100644 index 0000000000000..bf019bd43f2fd --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.test.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount, shallow } from 'enzyme'; + +import { ScheduleItemField } from './schedule_item_field'; +import { TestProviders, useFormFieldMock } from '../../../../common/mock'; + +describe('ScheduleItemField', () => { + it('renders correctly', () => { + const mockField = useFormFieldMock(); + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="schedule-item"]')).toHaveLength(1); + }); + + it('accepts a large number via user input', () => { + const mockField = useFormFieldMock(); + const wrapper = mount( + + + + ); + + wrapper + .find('[data-test-subj="interval"]') + .last() + .simulate('change', { target: { value: '5000000' } }); + + expect(mockField.setValue).toHaveBeenCalledWith('5000000s'); + }); + + it('clamps a number value greater than MAX_SAFE_INTEGER to MAX_SAFE_INTEGER', () => { + const unsafeInput = '99999999999999999999999'; + + const mockField = useFormFieldMock(); + const wrapper = mount( + + + + ); + + wrapper + .find('[data-test-subj="interval"]') + .last() + .simulate('change', { target: { value: unsafeInput } }); + + const expectedValue = `${Number.MAX_SAFE_INTEGER}s`; + expect(mockField.setValue).toHaveBeenCalledWith(expectedValue); + }); + + it('converts a non-numeric value to 0', () => { + const unsafeInput = 'this is not a number'; + + const mockField = useFormFieldMock(); + const wrapper = mount( + + + + ); + + wrapper + .find('[data-test-subj="interval"]') + .last() + .simulate('change', { target: { value: unsafeInput } }); + + expect(mockField.setValue).toHaveBeenCalledWith('0s'); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.tsx new file mode 100644 index 0000000000000..241e3869958a8 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/schedule_item_field.tsx @@ -0,0 +1,181 @@ +/* + * 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 { EuiSelectProps, EuiFieldNumberProps } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFieldNumber, + EuiFormRow, + EuiSelect, + transparentize, +} from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import styled from 'styled-components'; + +import type { FieldHook } from '../../../../shared_imports'; +import { getFieldValidityAndErrorMessage } from '../../../../shared_imports'; + +import * as I18n from './translations'; + +interface ScheduleItemProps { + field: FieldHook; + dataTestSubj: string; + idAria: string; + isDisabled: boolean; + minimumValue?: number; + timeTypes?: string[]; + fullWidth?: boolean; +} + +const timeTypeOptions = [ + { value: 's', text: I18n.SECONDS }, + { value: 'm', text: I18n.MINUTES }, + { value: 'h', text: I18n.HOURS }, + { value: 'd', text: I18n.DAYS }, +]; + +// move optional label to the end of input +const StyledLabelAppend = styled(EuiFlexItem)` + &.euiFlexItem { + margin-left: 31px; + } +`; + +const StyledEuiFormRow = styled(EuiFormRow)` + max-width: none; + + .euiFormControlLayout__append { + padding-inline: 0 !important; + } + + .euiFormControlLayoutIcons { + color: ${({ theme }) => theme.eui.euiColorPrimary}; + } +`; + +const MyEuiSelect = styled(EuiSelect)` + min-width: 106px; // Preserve layout when disabled & dropdown arrow is not rendered + background: ${({ theme }) => + transparentize(theme.eui.euiColorPrimary, 0.1)} !important; // Override focus states etc. + color: ${({ theme }) => theme.eui.euiColorPrimary}; + box-shadow: none; +`; + +const getNumberFromUserInput = (input: string, minimumValue = 0): number => { + const number = parseInt(input, 10); + if (Number.isNaN(number)) { + return minimumValue; + } else { + return Math.max(minimumValue, Math.min(number, Number.MAX_SAFE_INTEGER)); + } +}; + +export const ScheduleItemField = ({ + dataTestSubj, + field, + idAria, + isDisabled, + minimumValue = 0, + timeTypes = ['s', 'm', 'h'], + fullWidth = false, +}: ScheduleItemProps) => { + const [timeType, setTimeType] = useState(timeTypes[0]); + const [timeVal, setTimeVal] = useState(0); + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + const { value, setValue } = field; + + const onChangeTimeType = useCallback>( + (e) => { + setTimeType(e.target.value); + setValue(`${timeVal}${e.target.value}`); + }, + [setValue, timeVal] + ); + + const onChangeTimeVal = useCallback>( + (e) => { + const sanitizedValue = getNumberFromUserInput(e.target.value, minimumValue); + setTimeVal(sanitizedValue); + setValue(`${sanitizedValue}${timeType}`); + }, + [minimumValue, setValue, timeType] + ); + + useEffect(() => { + if (value !== `${timeVal}${timeType}`) { + const filterTimeVal = value.match(/\d+/g); + const filterTimeType = value.match(/[a-zA-Z]+/g); + if ( + !isEmpty(filterTimeVal) && + filterTimeVal != null && + !isNaN(Number(filterTimeVal[0])) && + Number(filterTimeVal[0]) !== Number(timeVal) + ) { + setTimeVal(Number(filterTimeVal[0])); + } + if ( + !isEmpty(filterTimeType) && + filterTimeType != null && + timeTypes.includes(filterTimeType[0]) && + filterTimeType[0] !== timeType + ) { + setTimeType(filterTimeType[0]); + } + } + }, [timeType, timeTypes, timeVal, value]); + + // EUI missing some props + const rest = { disabled: isDisabled }; + const label = useMemo( + () => ( + + + {field.label} + + + {field.labelAppend} + + + ), + [field.label, field.labelAppend] + ); + + return ( + + timeTypes.includes(type.value))} + onChange={onChangeTimeType} + value={timeType} + aria-label={field.label} + data-test-subj="timeType" + {...rest} + /> + } + fullWidth + min={minimumValue} + max={Number.MAX_SAFE_INTEGER} + onChange={onChangeTimeVal} + value={timeVal} + data-test-subj="interval" + {...rest} + /> + + ); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/translations.ts new file mode 100644 index 0000000000000..c460d2f7198b3 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation/components/schedule_item_field/translations.ts @@ -0,0 +1,36 @@ +/* + * 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 SECONDS = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRuleForm.secondsOptionDescription', + { + defaultMessage: 'Seconds', + } +); + +export const MINUTES = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRuleForm.minutesOptionDescription', + { + defaultMessage: 'Minutes', + } +); + +export const HOURS = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRuleForm.hoursOptionDescription', + { + defaultMessage: 'Hours', + } +); + +export const DAYS = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRuleForm.daysOptionDescription', + { + defaultMessage: 'Days', + } +); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx index 26c81f325ea18..a91487afc4486 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.tsx @@ -72,6 +72,8 @@ import { ALERT_SUPPRESSION_MISSING_FIELDS_FIELD_NAME, } from '../../../rule_creation/components/alert_suppression_edit'; import { THRESHOLD_ALERT_SUPPRESSION_ENABLED } from '../../../rule_creation/components/threshold_alert_suppression_edit'; +import { NEW_TERMS_FIELDS_LABEL } from '../../../rule_creation/components/new_terms_fields_edit/translations'; +import { HISTORY_WINDOW_START_LABEL } from '../../../rule_creation/components/history_window_start_edit/translations'; import type { FieldValueQueryBar } from '../query_bar_field'; const DescriptionListContainer = styled(EuiDescriptionList)` @@ -341,6 +343,9 @@ export const getDescriptionItem = ( } else if (field === 'threatMapping') { const threatMap: ThreatMapping = get(field, data); return buildThreatMappingDescription(label, threatMap); + } else if (field === 'newTermsFields') { + const values: string[] = get(field, data); + return buildStringArrayDescription(NEW_TERMS_FIELDS_LABEL, field, values); } else if (Array.isArray(get(field, data)) && field !== 'threatMapping') { const values: string[] = get(field, data); return buildStringArrayDescription(label, field, values); @@ -357,6 +362,9 @@ export const getDescriptionItem = ( } else if (field === 'maxSignals') { const value: number | undefined = get(field, data); return value ? [{ title: label, description: value }] : []; + } else if (field === 'historyWindowSize') { + const value: number = get(field, data); + return value ? [{ title: HISTORY_WINDOW_START_LABEL, description: value }] : []; } const description: string = get(field, data); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/index.tsx deleted file mode 100644 index 7dfb24200e645..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/new_terms_fields/index.tsx +++ /dev/null @@ -1,42 +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, { useMemo } from 'react'; - -import type { DataViewFieldBase } from '@kbn/es-query'; -import type { FieldHook } from '../../../../shared_imports'; -import { Field } from '../../../../shared_imports'; -import { NEW_TERMS_FIELD_PLACEHOLDER } from './translations'; - -interface NewTermsFieldsProps { - browserFields: DataViewFieldBase[]; - field: FieldHook; -} - -const FIELD_COMBO_BOX_WIDTH = 410; - -const fieldDescribedByIds = 'detectionEngineStepDefineRuleNewTermsField'; - -export const NewTermsFieldsComponent: React.FC = ({ - browserFields, - field, -}: NewTermsFieldsProps) => { - const fieldEuiFieldProps = useMemo( - () => ({ - fullWidth: true, - noSuggestions: false, - options: browserFields.map((browserField) => ({ label: browserField.name })), - placeholder: NEW_TERMS_FIELD_PLACEHOLDER, - onCreateOption: undefined, - style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, - }), - [browserFields] - ); - return ; -}; - -export const NewTermsFields = React.memo(NewTermsFieldsComponent); diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts index 045e957c4e129..fcbdfdf4f86b7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/rule_preview/helpers.ts @@ -107,7 +107,7 @@ export const getIsRulePreviewDisabled = ({ threatMapping, machineLearningJobId, queryBar, - newTermsFields, + newTermsFields = [], }: { ruleType: Type; isQueryBarValid: boolean; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx index 7b056b5969799..f7845c31378af 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/index.tsx @@ -55,7 +55,6 @@ import { } from '../../../../shared_imports'; import type { FormHook, FieldHook } from '../../../../shared_imports'; import { schema } from './schema'; -import { getTermsAggregationFields } from './utils'; import { useExperimentalFeatureFieldsTransform } from './use_experimental_feature_fields_transform'; import * as i18n from './translations'; import { @@ -72,8 +71,6 @@ import { EqlQueryEdit } from '../../../rule_creation/components/eql_query_edit'; import { DataViewSelectorField } from '../data_view_selector_field'; import { ThreatMatchInput } from '../threatmatch_input'; import { useFetchIndex } from '../../../../common/containers/source'; -import { NewTermsFields } from '../new_terms_fields'; -import { ScheduleItem } from '../../../rule_creation/components/schedule_item_form'; import { RequiredFields } from '../../../rule_creation/components/required_fields'; import { DocLink } from '../../../../common/components/links_to_docs/doc_link'; import { useLicense } from '../../../../common/hooks/use_license'; @@ -90,6 +87,10 @@ import { } from '../../../rule_creation/components/alert_suppression_edit'; import { ThresholdAlertSuppressionEdit } from '../../../rule_creation/components/threshold_alert_suppression_edit'; import { usePersistentAlertSuppressionState } from './use_persistent_alert_suppression_state'; +import { useTermsAggregationFields } from '../../../../common/hooks/use_terms_aggregation_fields'; +import { HistoryWindowStartEdit } from '../../../rule_creation/components/history_window_start_edit'; +import { NewTermsFieldsEdit } from '../../../rule_creation/components/new_terms_fields_edit'; +import { usePersistentNewTermsState } from './use_persistent_new_terms_state'; import { EsqlQueryEdit } from '../../../rule_creation/components/esql_query_edit'; import { usePersistentQuery } from './use_persistent_query'; @@ -211,18 +212,11 @@ const StepDefineRuleComponent: FC = ({ () => (indexPattern.fields as FieldSpec[]).filter((field) => field.aggregatable === true), [indexPattern.fields] ); - const termsAggregationFields = useMemo( - /** - * Typecasting to FieldSpec because fields is - * typed as DataViewFieldBase[] which does not have - * the 'aggregatable' property, however the type is incorrect - * - * fields does contain elements with the aggregatable property. - * We will need to determine where these types are defined and - * figure out where the discrepency is. - */ - () => getTermsAggregationFields(indexPattern.fields as FieldSpec[]), - [indexPattern.fields] + + const termsAggregationFields = useTermsAggregationFields(indexPattern.fields); + const termsAggregationFieldNames = useMemo( + () => termsAggregationFields.map((field) => field.name), + [termsAggregationFields] ); const [threatIndexPatternsLoading, { indexPatterns: threatIndexPatterns }] = @@ -245,6 +239,12 @@ const StepDefineRuleComponent: FC = ({ form, }); usePersistentAlertSuppressionState({ form }); + usePersistentNewTermsState({ + form, + ruleTypePath: 'ruleType', + newTermsFieldsPath: 'newTermsFields', + historyWindowStartPath: 'historyWindowSize', + }); const handleSetRuleFromTimeline = useCallback( ({ index: timelineIndex, queryBar: timelineQueryBar, eqlOptions }) => { @@ -730,30 +730,14 @@ const StepDefineRuleComponent: FC = ({ - - <> - - - - + {isNewTermsRule(ruleType) && ( + + <> + + + + + )} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx index 323e321eee8d9..4f168d94388d5 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx @@ -17,12 +17,10 @@ import { } from '../../../../common/components/threat_match/helpers'; import { isEsqlRule, - isNewTermsRule, isThreatMatchRule, isThresholdRule, isSuppressionRuleConfiguredWithGroupBy, } from '../../../../../common/detection_engine/utils'; -import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../../common/constants'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; import type { ERROR_CODE, FormSchema, ValidationFunc } from '../../../../shared_imports'; import { FIELD_TYPES, fieldValidators } from '../../../../shared_imports'; @@ -478,106 +476,8 @@ export const schema: FormSchema = { }, ], }, - newTermsFields: { - type: FIELD_TYPES.COMBO_BOX, - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel', - { - defaultMessage: 'Fields', - } - ), - helpText: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNewTermsFieldHelpText', - { - defaultMessage: 'Select a field to check for new terms.', - } - ), - validations: [ - { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ formData }] = args; - const needsValidation = isNewTermsRule(formData.ruleType); - if (!needsValidation) { - return; - } - - return fieldValidators.emptyField( - i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin', - { - defaultMessage: 'A minimum of one field is required.', - } - ) - )(...args); - }, - }, - { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ formData }] = args; - const needsValidation = isNewTermsRule(formData.ruleType); - if (!needsValidation) { - return; - } - return fieldValidators.maxLengthField({ - length: MAX_NUMBER_OF_NEW_TERMS_FIELDS, - message: i18n.translate( - 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsFieldsMax', - { - defaultMessage: 'Number of fields must be 3 or less.', - } - ), - })(...args); - }, - }, - ], - }, - historyWindowSize: { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.historyWindowSizeLabel', - { - defaultMessage: 'History Window Size', - } - ), - helpText: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.historyWindowSizeHelpText', - { - defaultMessage: "New terms rules only alert if terms don't appear in historical data.", - } - ), - validations: [ - { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ path, formData }] = args; - const needsValidation = isNewTermsRule(formData.ruleType); - - if (!needsValidation) { - return; - } - - const filterTimeVal = formData.historyWindowSize.match(/\d+/g); - - if (filterTimeVal <= 0) { - return { - code: 'ERR_MIN_LENGTH', - path, - message: i18n.translate( - 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.historyWindowSize.errMin', - { - defaultMessage: 'History window size must be greater than 0.', - } - ), - }; - } - }, - }, - ], - }, + newTermsFields: {}, + historyWindowSize: {}, [ALERT_SUPPRESSION_FIELDS_FIELD_NAME]: { validations: [ { diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_persistent_new_terms_state.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_persistent_new_terms_state.tsx new file mode 100644 index 0000000000000..61bfa45ba8a72 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_persistent_new_terms_state.tsx @@ -0,0 +1,73 @@ +/* + * 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 { useEffect, useRef } from 'react'; +import usePrevious from 'react-use/lib/usePrevious'; +import { isNewTermsRule } from '../../../../../common/detection_engine/utils'; +import type { FormHook } from '../../../../shared_imports'; +import { useFormData } from '../../../../shared_imports'; +import { type DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; +import { + type NewTermsFields, + type HistoryWindowStart, +} from '../../../../../common/api/detection_engine'; + +interface UsePersistentNewTermsStateParams { + form: FormHook; + ruleTypePath: string; + newTermsFieldsPath: string; + historyWindowStartPath: string; +} + +interface LastNewTermsState { + newTermsFields: NewTermsFields; + historyWindowStart: HistoryWindowStart; +} + +export function usePersistentNewTermsState({ + form, + ruleTypePath, + newTermsFieldsPath, + historyWindowStartPath, +}: UsePersistentNewTermsStateParams): void { + const lastNewTermsState = useRef(); + const [formData] = useFormData({ form, watch: [newTermsFieldsPath, historyWindowStartPath] }); + + const { + [ruleTypePath]: ruleType, + [newTermsFieldsPath]: newTermsFields, + [historyWindowStartPath]: historyWindowStart, + } = formData; + const previousRuleType = usePrevious(ruleType); + + useEffect(() => { + if ( + isNewTermsRule(ruleType) && + !isNewTermsRule(previousRuleType) && + lastNewTermsState.current + ) { + form.updateFieldValues({ + [newTermsFieldsPath]: lastNewTermsState.current.newTermsFields, + [historyWindowStartPath]: lastNewTermsState.current.historyWindowStart, + }); + + return; + } + + if (isNewTermsRule(ruleType)) { + lastNewTermsState.current = { newTermsFields, historyWindowStart }; + } + }, [ + form, + ruleType, + previousRuleType, + newTermsFieldsPath, + historyWindowStartPath, + newTermsFields, + historyWindowStart, + ]); +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_schedule_rule/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_schedule_rule/index.tsx index 2197b069de404..7c309ed32a68b 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_schedule_rule/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_schedule_rule/index.tsx @@ -13,11 +13,11 @@ import type { ScheduleStepRule, } from '../../../../detections/pages/detection_engine/rules/types'; import { StepRuleDescription } from '../description_step'; -import { ScheduleItem } from '../../../rule_creation/components/schedule_item_form'; import { Form, UseField } from '../../../../shared_imports'; import type { FormHook } from '../../../../shared_imports'; import { StepContentWrapper } from '../../../rule_creation/components/step_content_wrapper'; import { schema } from './schema'; +import { ScheduleItemField } from '../../../rule_creation/components/schedule_item_field'; const StyledForm = styled(Form)` max-width: 235px !important; @@ -43,7 +43,7 @@ const StepScheduleRuleComponent: FC = ({ = ({ /> { const timeObj: { unit: Unit; value: number } = { @@ -530,7 +531,7 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep query: ruleFields.queryBar?.query?.query as string, required_fields: requiredFields, new_terms_fields: ruleFields.newTermsFields, - history_window_start: `now-${ruleFields.historyWindowSize}`, + history_window_start: convertDurationToDateMath(ruleFields.historyWindowSize), ...alertSuppressionFields, } : isEsqlFields(ruleFields) && !('index' in ruleFields) diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx index 177ff4e18d426..de4fce3da3686 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx @@ -41,7 +41,6 @@ import { useGetSavedQuery } from '../../../../detections/pages/detection_engine/ import * as threatMatchI18n from '../../../../common/components/threat_match/translations'; import * as timelinesI18n from '../../../../timelines/components/timeline/translations'; import type { Duration } from '../../../../detections/pages/detection_engine/rules/types'; -import { convertHistoryStartToSize } from '../../../../detections/pages/detection_engine/rules/helpers'; import { MlJobsDescription } from '../../../rule_creation/components/ml_jobs_description/ml_jobs_description'; import { MlJobLink } from '../../../rule_creation/components/ml_job_link/ml_job_link'; import { useSecurityJobs } from '../../../../common/components/ml_popover/hooks/use_security_jobs'; @@ -58,6 +57,8 @@ import { } from './rule_definition_section.styles'; import { getQueryLanguageLabel } from './helpers'; import { useDefaultIndexPattern } from '../../hooks/use_default_index_pattern'; +import { convertDateMathToDuration } from '../../../../common/utils/date_math'; +import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../common/constants'; import { EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL, EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL, @@ -435,7 +436,9 @@ interface HistoryWindowSizeProps { } export const HistoryWindowSize = ({ historyWindowStart }: HistoryWindowSizeProps) => { - const size = historyWindowStart ? convertHistoryStartToSize(historyWindowStart) : '7d'; + const size = historyWindowStart + ? convertDateMathToDuration(historyWindowStart) + : DEFAULT_HISTORY_WINDOW_SIZE; return ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_adapter.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_adapter.tsx new file mode 100644 index 0000000000000..084a81e3b9599 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_adapter.tsx @@ -0,0 +1,13 @@ +/* + * 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 { HistoryWindowStartEdit } from '../../../../../../../rule_creation/components/history_window_start_edit'; + +export function HistoryWindowStartEditAdapter(): JSX.Element { + return ; +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_form.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_form.tsx new file mode 100644 index 0000000000000..87ac2fee64e7b --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/history_window_start/history_window_start_edit_form.tsx @@ -0,0 +1,48 @@ +/* + * 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 { type FormData } from '../../../../../../../../shared_imports'; +import { HistoryWindowStartEditAdapter } from './history_window_start_edit_adapter'; +import type { HistoryWindowStart } from '../../../../../../../../../common/api/detection_engine'; +import { + convertDurationToDateMath, + convertDateMathToDuration, +} from '../../../../../../../../common/utils/date_math'; +import { DEFAULT_HISTORY_WINDOW_SIZE } from '../../../../../../../../common/constants'; +import { RuleFieldEditFormWrapper } from '../../../field_final_side'; + +export function HistoryWindowStartEditForm(): JSX.Element { + return ( + + ); +} + +interface HistoryWindowFormData { + historyWindowSize: HistoryWindowStart; +} + +function deserializer(defaultValue: FormData): HistoryWindowFormData { + return { + historyWindowSize: defaultValue.history_window_start + ? convertDateMathToDuration(defaultValue.history_window_start) + : DEFAULT_HISTORY_WINDOW_SIZE, + }; +} + +function serializer(formData: FormData): { history_window_start: HistoryWindowStart } { + return { + history_window_start: convertDurationToDateMath(formData.historyWindowSize), + }; +} + +const schema = {}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_adapter.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_adapter.tsx new file mode 100644 index 0000000000000..476bf9869bd96 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_adapter.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { NewTermsFieldsEdit } from '../../../../../../../rule_creation/components/new_terms_fields_edit'; +import { useDiffableRuleDataView } from '../hooks/use_diffable_rule_data_view'; +import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine'; +import { useTermsAggregationFields } from '../../../../../../../../common/hooks/use_terms_aggregation_fields'; + +interface NewTermsFieldsEditAdapterProps { + finalDiffableRule: DiffableRule; +} + +export function NewTermsFieldsEditAdapter({ + finalDiffableRule, +}: NewTermsFieldsEditAdapterProps): JSX.Element { + const { dataView } = useDiffableRuleDataView(finalDiffableRule); + const termsAggregationFields = useTermsAggregationFields(dataView?.fields ?? []); + const fieldNames = termsAggregationFields.map((field) => field.name); + + return ; +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_form.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_form.tsx new file mode 100644 index 0000000000000..9b8d709b27f06 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/new_terms_fields/new_terms_fields_edit_form.tsx @@ -0,0 +1,18 @@ +/* + * 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 { RuleFieldEditFormWrapper } from '../../../field_final_side'; +import { NewTermsFieldsEditAdapter } from './new_terms_fields_edit_adapter'; + +export function NewTermsFieldsEditForm(): JSX.Element { + return ( + + ); +} + +const schema = {}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_schedule.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_schedule.tsx index 12e7bbea9a20a..3acd7c3050fbb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_schedule.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/fields/rule_schedule.tsx @@ -10,8 +10,8 @@ import { parseDuration } from '@kbn/alerting-plugin/common'; import { type FormSchema, type FormData, UseField } from '../../../../../../../shared_imports'; import { schema } from '../../../../../../rule_creation_ui/components/step_schedule_rule/schema'; import type { RuleSchedule } from '../../../../../../../../common/api/detection_engine'; -import { ScheduleItem } from '../../../../../../rule_creation/components/schedule_item_form'; import { secondsToDurationString } from '../../../../../../../detections/pages/detection_engine/rules/helpers'; +import { ScheduleItemField } from '../../../../../../rule_creation/components/schedule_item_field'; export const ruleScheduleSchema = { interval: schema.interval, @@ -28,8 +28,8 @@ const componentProps = { export function RuleScheduleEdit(): JSX.Element { return ( <> - - + + ); } diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/new_terms_rule_field_edit.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/new_terms_rule_field_edit.tsx index e2860d431affa..f47607abe3844 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/new_terms_rule_field_edit.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/three_way_diff/final_edit/new_terms_rule_field_edit.tsx @@ -7,9 +7,12 @@ import React from 'react'; import type { UpgradeableNewTermsFields } from '../../../../model/prebuilt_rule_upgrade/fields'; -import { KqlQueryEditForm } from './fields/kql_query'; -import { DataSourceEditForm } from './fields/data_source'; import { AlertSuppressionEditForm } from './fields/alert_suppression'; +import { DataSourceEditForm } from './fields/data_source'; +import { HistoryWindowStartEditForm } from './fields/history_window_start/history_window_start_edit_form'; +import { KqlQueryEditForm } from './fields/kql_query'; +import { NewTermsFieldsEditForm } from './fields/new_terms_fields/new_terms_fields_edit_form'; +import { assertUnreachable } from '../../../../../../../common/utility_types'; interface NewTermsRuleFieldEditProps { fieldName: UpgradeableNewTermsFields; @@ -17,13 +20,17 @@ interface NewTermsRuleFieldEditProps { export function NewTermsRuleFieldEdit({ fieldName }: NewTermsRuleFieldEditProps) { switch (fieldName) { - case 'kql_query': - return ; - case 'data_source': - return ; case 'alert_suppression': return ; + case 'data_source': + return ; + case 'history_window_start': + return ; + case 'kql_query': + return ; + case 'new_terms_fields': + return ; default: - return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented + return assertUnreachable(fieldName); } } diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx index 88e1411a5e0bc..518c18a59a413 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/schedule_form.tsx @@ -9,11 +9,11 @@ import { EuiCallOut } from '@elastic/eui'; import React, { useCallback } from 'react'; import type { BulkActionEditPayload } from '../../../../../../../common/api/detection_engine/rule_management'; import { BulkActionEditTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management'; -import { ScheduleItem } from '../../../../../rule_creation/components/schedule_item_form'; import type { FormSchema } from '../../../../../../shared_imports'; import { UseField, useForm } from '../../../../../../shared_imports'; import { bulkSetSchedule as i18n } from '../translations'; import { BulkEditFormWrapper } from './bulk_edit_form_wrapper'; +import { ScheduleItemField } from '../../../../../rule_creation/components/schedule_item_field'; export interface ScheduleFormData { interval: string; @@ -79,7 +79,7 @@ export const ScheduleForm = ({ rulesCount, onClose, onConfirm }: ScheduleFormCom > ({ newTermsFields: ('new_terms_fields' in rule && rule.new_terms_fields) || [], historyWindowSize: 'history_window_start' in rule && rule.history_window_start - ? convertHistoryStartToSize(rule.history_window_start) - : '7d', + ? convertDateMathToDuration(rule.history_window_start) + : DEFAULT_HISTORY_WINDOW_SIZE, shouldLoadQueryDynamically: Boolean(rule.type === 'saved_query' && rule.saved_id), [ALERT_SUPPRESSION_FIELDS_FIELD_NAME]: ('alert_suppression' in rule && @@ -189,14 +191,6 @@ export const getDefineStepsData = (rule: RuleResponse): DefineStepRule => ({ ), }); -export const convertHistoryStartToSize = (relativeTime: string) => { - if (relativeTime.startsWith('now-')) { - return relativeTime.substring(4); - } else { - return relativeTime; - } -}; - export const getScheduleStepsData = (rule: RuleResponse): ScheduleStepRule => { const { interval, from } = rule; const fromHumanizedValue = getHumanizedDuration(from, interval); diff --git a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts index 903b463d6f585..483cbf06b6256 100644 --- a/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/screens/create_new_rule.ts @@ -275,10 +275,10 @@ export const ESQL_QUERY_BAR = '[data-test-subj="ruleEsqlQueryBar"]'; export const NEW_TERMS_INPUT_AREA = '[data-test-subj="newTermsInput"]'; export const NEW_TERMS_HISTORY_SIZE = - '[data-test-subj="detectionEngineStepDefineRuleHistoryWindowSize"] [data-test-subj="interval"]'; + '[data-test-subj="historyWindowSize"] [data-test-subj="interval"]'; export const NEW_TERMS_HISTORY_TIME_TYPE = - '[data-test-subj="detectionEngineStepDefineRuleHistoryWindowSize"] [data-test-subj="timeType"]'; + '[data-test-subj="historyWindowSize"] [data-test-subj="timeType"]'; export const LOAD_QUERY_DYNAMICALLY_CHECKBOX = '[data-test-subj="detectionEngineStepDefineRuleShouldLoadQueryDynamically"] input';