css`
overflow-y: auto;
+ ${scrollBarStyles}
`;
const newChatButtonWrapperClassName = css`
@@ -56,10 +59,12 @@ export function ConversationList({
onClickNewChat: () => void;
onClickDeleteConversation: (id: string) => void;
}) {
+ const euiTheme = useEuiTheme();
+ const scrollBarStyles = euiScrollBarStyles(euiTheme);
return (
-
+
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 549c459b4944f..1c0f90d9ad92e 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -28790,8 +28790,6 @@
"xpack.observability.slo.alerting.burnRate.reasonForInstanceId": "{actionGroupName} : Le taux d'avancement pour le (les) dernier(s) {longWindowDuration} est de {longWindowBurnRate} et pour le (les) dernier(s) {shortWindowDuration} est de {shortWindowBurnRate} pour {instanceId}. Alerter si supérieur à {burnRateThreshold} pour les deux fenêtres",
"xpack.observability.slo.burnRate.breachedStatustSubtitle": "Au rythme actuel, le budget d'erreur sera épuisé en {hour} heures.",
"xpack.observability.slo.burnRate.threshold": "Le seuil est {threshold}x",
- "xpack.observability.slo.clone.errorNotification": "Échec du clonage de {name}",
- "xpack.observability.slo.clone.successNotification": "{name} créé avec succès",
"xpack.observability.slo.create.errorNotification": "Un problème est survenu lors de la création de {name}",
"xpack.observability.slo.create.successNotification": "{name} créé avec succès",
"xpack.observability.slo.deleteConfirmationModal.title": "Supprimer {name} ?",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 8ab33bb6186bb..cc52a3743aca8 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -28790,8 +28790,6 @@
"xpack.observability.slo.alerting.burnRate.reasonForInstanceId": "{actionGroupName}:過去{longWindowDuration}のバーンレートは{longWindowBurnRate}、{instanceId}の過去{shortWindowDuration}のバーンレートは{shortWindowBurnRate}です。両期間とも{burnRateThreshold}を超えたらアラート",
"xpack.observability.slo.burnRate.breachedStatustSubtitle": "現在のレートでは、エラー予算は{hour}時間後に使い果たされます。",
"xpack.observability.slo.burnRate.threshold": "しきい値は{threshold}xです",
- "xpack.observability.slo.clone.errorNotification": "{name}を複製できませんでした",
- "xpack.observability.slo.clone.successNotification": "{name}の作成が正常に完了しました",
"xpack.observability.slo.create.errorNotification": "{name}の作成中に問題が発生しました",
"xpack.observability.slo.create.successNotification": "{name}の作成が正常に完了しました",
"xpack.observability.slo.deleteConfirmationModal.title": "{name}を削除しますか?",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index d12e878252732..4a724bbb7c4a6 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -28787,8 +28787,6 @@
"xpack.observability.slo.alerting.burnRate.reasonForInstanceId": "{actionGroupName}:过去 {longWindowDuration} 的消耗速度为 {longWindowBurnRate},且对于 {instanceId},过去 {shortWindowDuration} 为 {shortWindowBurnRate}。两个窗口超出 {burnRateThreshold} 时告警",
"xpack.observability.slo.burnRate.breachedStatustSubtitle": "按照当前的速率,错误预算将在 {hour} 小时后耗尽。",
"xpack.observability.slo.burnRate.threshold": "阈值为 {threshold}x",
- "xpack.observability.slo.clone.errorNotification": "无法克隆 {name}",
- "xpack.observability.slo.clone.successNotification": "已成功创建 {name}",
"xpack.observability.slo.create.errorNotification": "创建 {name} 时出现问题",
"xpack.observability.slo.create.successNotification": "已成功创建 {name}",
"xpack.observability.slo.deleteConfirmationModal.title": "删除 {name}?",
diff --git a/x-pack/test/cloud_security_posture_api/config.ts b/x-pack/test/cloud_security_posture_api/config.ts
index e7a34bafe9fb5..625236022cd14 100644
--- a/x-pack/test/cloud_security_posture_api/config.ts
+++ b/x-pack/test/cloud_security_posture_api/config.ts
@@ -18,6 +18,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('./telemetry/telemetry.ts'),
require.resolve('./routes/vulnerabilities_dashboard.ts'),
require.resolve('./routes/stats.ts'),
+ require.resolve('./routes/csp_benchmark_rules_bulk_update.ts'),
],
junit: {
reportName: 'X-Pack Cloud Security Posture API Tests',
diff --git a/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts b/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts
new file mode 100644
index 0000000000000..dad7845e60e31
--- /dev/null
+++ b/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts
@@ -0,0 +1,247 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import expect from '@kbn/expect';
+import { expect as expectExpect } from 'expect';
+
+import {
+ ELASTIC_HTTP_VERSION_HEADER,
+ X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
+} from '@kbn/core-http-common';
+import type { FtrProviderContext } from '../ftr_provider_context';
+
+interface RuleIdentifier {
+ benchmarkId: string;
+ benchmarkVersion: string;
+ ruleNumber: string;
+}
+
+// eslint-disable-next-line import/no-default-export
+export default function ({ getService }: FtrProviderContext) {
+ const retry = getService('retry');
+ const supertest = getService('supertest');
+ const log = getService('log');
+ const kibanaServer = getService('kibanaServer');
+
+ const generateRuleKey = (ruleParams: RuleIdentifier): string => {
+ return `${ruleParams.benchmarkId};${ruleParams.benchmarkVersion};${ruleParams.ruleNumber}`;
+ };
+
+ const generateRandomRule = (): RuleIdentifier => {
+ const majorVersionNumber = Math.floor(Math.random() * 10); // Random major number between 0 and 9
+ const minorVersionNumber = Math.floor(Math.random() * 10);
+ const benchmarksIds = ['cis_aws', 'cis_k8s', 'cis_k8s'];
+ const benchmarksVersions = ['v2.0.0', 'v2.0.1', 'v2.0.3', 'v3.0.0'];
+ const randomBenchmarkId = benchmarksIds[Math.floor(Math.random() * benchmarksIds.length)];
+ const randomBenchmarkVersion =
+ benchmarksVersions[Math.floor(Math.random() * benchmarksVersions.length)];
+
+ return {
+ benchmarkId: randomBenchmarkId,
+ benchmarkVersion: randomBenchmarkVersion,
+ ruleNumber: `${majorVersionNumber}.${minorVersionNumber}`,
+ };
+ };
+
+ /**
+ * required before indexing findings
+ */
+ const waitForPluginInitialized = (): Promise =>
+ retry.try(async () => {
+ log.debug('Check CSP plugin is initialized');
+ const response = await supertest
+ .get('/internal/cloud_security_posture/status?check=init')
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
+ .expect(200);
+ expect(response.body).to.eql({ isPluginInitialized: true });
+ log.debug('CSP plugin is initialized');
+ });
+
+ describe('Verify update csp rules states API', async () => {
+ before(async () => {
+ await waitForPluginInitialized();
+ });
+
+ afterEach(async () => {
+ await kibanaServer.savedObjects.clean({
+ types: ['cloud-security-posture-settings'],
+ });
+ });
+
+ it('mute rules successfully', async () => {
+ const rule1 = generateRandomRule();
+ const rule2 = generateRandomRule();
+
+ const { body } = await supertest
+ .post(`/internal/cloud_security_posture/rules/_bulk_action`)
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ action: 'mute',
+ rules: [
+ {
+ benchmark_id: rule1.benchmarkId,
+ benchmark_version: rule1.benchmarkVersion,
+ rule_number: rule1.ruleNumber,
+ },
+ {
+ benchmark_id: rule2.benchmarkId,
+ benchmark_version: rule2.benchmarkVersion,
+ rule_number: rule2.ruleNumber,
+ },
+ ],
+ })
+ .expect(200);
+
+ expectExpect(body.updated_benchmark_rules).toEqual(
+ expectExpect.objectContaining({
+ [generateRuleKey(rule1)]: { muted: true },
+ [generateRuleKey(rule2)]: { muted: true },
+ })
+ );
+ });
+
+ it('unmute rules successfully', async () => {
+ const rule1 = generateRandomRule();
+ const rule2 = generateRandomRule();
+
+ const { body } = await supertest
+ .post(`/internal/cloud_security_posture/rules/_bulk_action`)
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ action: 'unmute',
+ rules: [
+ {
+ benchmark_id: rule1.benchmarkId,
+ benchmark_version: rule1.benchmarkVersion,
+ rule_number: rule1.ruleNumber,
+ },
+ {
+ benchmark_id: rule2.benchmarkId,
+ benchmark_version: rule2.benchmarkVersion,
+ rule_number: rule2.ruleNumber,
+ },
+ ],
+ })
+ .expect(200);
+
+ expectExpect(body.updated_benchmark_rules).toEqual(
+ expectExpect.objectContaining({
+ [generateRuleKey(rule1)]: { muted: false },
+ [generateRuleKey(rule2)]: { muted: false },
+ })
+ );
+ });
+
+ it('verify new rules are added and existing rules are set.', async () => {
+ const rule1 = generateRandomRule();
+ const rule2 = generateRandomRule();
+ const rule3 = generateRandomRule();
+
+ // unmute rule1 and rule2
+ const cspSettingsResponse = await supertest
+ .post(`/internal/cloud_security_posture/rules/_bulk_action`)
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ action: 'unmute',
+ rules: [
+ {
+ benchmark_id: rule1.benchmarkId,
+ benchmark_version: rule1.benchmarkVersion,
+ rule_number: rule1.ruleNumber,
+ },
+ {
+ benchmark_id: rule2.benchmarkId,
+ benchmark_version: rule2.benchmarkVersion,
+ rule_number: rule2.ruleNumber,
+ },
+ ],
+ })
+ .expect(200);
+
+ expectExpect(cspSettingsResponse.body.updated_benchmark_rules).toEqual(
+ expectExpect.objectContaining({
+ [generateRuleKey(rule1)]: { muted: false },
+ [generateRuleKey(rule2)]: { muted: false },
+ })
+ );
+
+ // mute rule1 and rule3
+ const updatedCspSettingsResponse = await supertest
+ .post(`/internal/cloud_security_posture/rules/_bulk_action`)
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ action: 'mute',
+ rules: [
+ {
+ benchmark_id: rule1.benchmarkId,
+ benchmark_version: rule1.benchmarkVersion,
+ rule_number: rule1.ruleNumber,
+ },
+ {
+ benchmark_id: rule3.benchmarkId,
+ benchmark_version: rule3.benchmarkVersion,
+ rule_number: rule3.ruleNumber,
+ },
+ ],
+ })
+ .expect(200);
+
+ expectExpect(updatedCspSettingsResponse.body.updated_benchmark_rules).toEqual(
+ expectExpect.objectContaining({
+ [generateRuleKey(rule1)]: { muted: true },
+ [generateRuleKey(rule3)]: { muted: true },
+ })
+ );
+ });
+
+ it('set wrong action input', async () => {
+ const rule1 = generateRandomRule();
+
+ const { body } = await supertest
+ .post(`/internal/cloud_security_posture/rules/_bulk_action`)
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ action: 'foo',
+ rules: [
+ {
+ benchmark_id: rule1.benchmarkId,
+ benchmark_version: rule1.benchmarkVersion,
+ rule_number: rule1.ruleNumber,
+ },
+ ],
+ });
+
+ expect(body.error).to.eql('Bad Request');
+ expect(body.statusCode).to.eql(400);
+ });
+
+ it('set wrong rule ids input', async () => {
+ const { body } = await supertest
+ .post(`/internal/cloud_security_posture/rules/_bulk_action`)
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
+ .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ action: 'mute',
+ rule_ids: ['invalid_rule_structure'],
+ });
+
+ expect(body.error).to.eql('Bad Request');
+ expect(body.statusCode).to.eql(400);
+ });
+ });
+}
diff --git a/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts b/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts
new file mode 100644
index 0000000000000..ba38cc432ff8c
--- /dev/null
+++ b/x-pack/test/dataset_quality_api_integration/tests/data_streams/degraded_docs.spec.ts
@@ -0,0 +1,98 @@
+/*
+ * 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 { log, timerange } from '@kbn/apm-synthtrace-client';
+import expect from '@kbn/expect';
+import { DatasetQualityApiClientKey } from '../../common/config';
+import { FtrProviderContext } from '../../common/ftr_provider_context';
+
+export default function ApiTest({ getService }: FtrProviderContext) {
+ const registry = getService('registry');
+ const synthtrace = getService('logSynthtraceEsClient');
+ const datasetQualityApiClient = getService('datasetQualityApiClient');
+ const start = '2023-12-11T18:00:00.000Z';
+ const end = '2023-12-11T18:01:00.000Z';
+
+ async function callApiAs(user: DatasetQualityApiClientKey) {
+ return await datasetQualityApiClient[user]({
+ endpoint: 'GET /internal/dataset_quality/data_streams/degraded_docs',
+ params: {
+ query: {
+ type: 'logs',
+ start,
+ end,
+ },
+ },
+ });
+ }
+
+ registry.when('Degraded docs', { config: 'basic' }, () => {
+ describe('and there are log documents', () => {
+ before(async () => {
+ await synthtrace.index([
+ timerange(start, end)
+ .interval('1m')
+ .rate(1)
+ .generator((timestamp) =>
+ log
+ .create()
+ .message('This is a log message')
+ .timestamp(timestamp)
+ .dataset('synth.1')
+ .defaults({
+ 'log.file.path': '/my-service.log',
+ })
+ ),
+ timerange(start, end)
+ .interval('1m')
+ .rate(1)
+ .generator((timestamp) =>
+ log
+ .create()
+ .message('This is a log message')
+ .timestamp(timestamp)
+ .dataset('synth.2')
+ .logLevel(
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?'
+ )
+ .defaults({
+ 'log.file.path': '/my-service.log',
+ })
+ ),
+ ]);
+ });
+
+ it('returns stats correctly', async () => {
+ const stats = await callApiAs('datasetQualityLogsUser');
+ expect(stats.body.degradedDocs.length).to.be(2);
+
+ const percentages = stats.body.degradedDocs.reduce(
+ (acc, curr) => ({
+ ...acc,
+ [curr.dataset]: curr.percentage,
+ }),
+ {} as Record
+ );
+
+ expect(percentages['logs-synth.1-default']).to.be(0);
+ expect(percentages['logs-synth.2-default']).to.be(100);
+ });
+
+ after(async () => {
+ await synthtrace.clean();
+ });
+ });
+
+ describe('and there are not log documents', () => {
+ it('returns stats correctly', async () => {
+ const stats = await callApiAs('datasetQualityLogsUser');
+
+ expect(stats.body.degradedDocs.length).to.be(0);
+ });
+ });
+ });
+}
diff --git a/x-pack/test/dataset_quality_api_integration/tests/es_utils.ts b/x-pack/test/dataset_quality_api_integration/tests/data_streams/es_utils.ts
similarity index 100%
rename from x-pack/test/dataset_quality_api_integration/tests/es_utils.ts
rename to x-pack/test/dataset_quality_api_integration/tests/data_streams/es_utils.ts
diff --git a/x-pack/test/dataset_quality_api_integration/tests/data_streams.spec.ts b/x-pack/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts
similarity index 86%
rename from x-pack/test/dataset_quality_api_integration/tests/data_streams.spec.ts
rename to x-pack/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts
index 6d11326bf213e..7fa60dcb4b118 100644
--- a/x-pack/test/dataset_quality_api_integration/tests/data_streams.spec.ts
+++ b/x-pack/test/dataset_quality_api_integration/tests/data_streams/stats.spec.ts
@@ -7,10 +7,10 @@
import { log, timerange } from '@kbn/apm-synthtrace-client';
import expect from '@kbn/expect';
-import { DatasetQualityApiClientKey } from '../common/config';
-import { DatasetQualityApiError } from '../common/dataset_quality_api_supertest';
-import { FtrProviderContext } from '../common/ftr_provider_context';
-import { expectToReject } from '../utils';
+import { DatasetQualityApiClientKey } from '../../common/config';
+import { DatasetQualityApiError } from '../../common/dataset_quality_api_supertest';
+import { FtrProviderContext } from '../../common/ftr_provider_context';
+import { expectToReject } from '../../utils';
import { cleanLogIndexTemplate, addIntegrationToLogIndexTemplate } from './es_utils';
export default function ApiTest({ getService }: FtrProviderContext) {
@@ -43,7 +43,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
describe('when required privileges are set', () => {
- describe('and uncategorized datastreams', () => {
+ describe('and categorized datastreams', () => {
const integration = 'my-custom-integration';
before(async () => {
@@ -67,8 +67,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(stats.body.dataStreamsStats.length).to.be(1);
expect(stats.body.dataStreamsStats[0].integration).to.be(integration);
expect(stats.body.dataStreamsStats[0].size).not.empty();
- expect(stats.body.dataStreamsStats[0].size_bytes).greaterThan(0);
- expect(stats.body.dataStreamsStats[0].last_activity).greaterThan(0);
+ expect(stats.body.dataStreamsStats[0].sizeBytes).greaterThan(0);
+ expect(stats.body.dataStreamsStats[0].lastActivity).greaterThan(0);
});
after(async () => {
@@ -77,7 +77,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
});
- describe('and categorized datastreams', () => {
+ describe('and uncategorized datastreams', () => {
before(async () => {
await synthtrace.index([
timerange('2023-11-20T15:00:00.000Z', '2023-11-20T15:01:00.000Z')
@@ -97,8 +97,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(stats.body.dataStreamsStats.length).to.be(1);
expect(stats.body.dataStreamsStats[0].integration).not.ok();
expect(stats.body.dataStreamsStats[0].size).not.empty();
- expect(stats.body.dataStreamsStats[0].size_bytes).greaterThan(0);
- expect(stats.body.dataStreamsStats[0].last_activity).greaterThan(0);
+ expect(stats.body.dataStreamsStats[0].sizeBytes).greaterThan(0);
+ expect(stats.body.dataStreamsStats[0].lastActivity).greaterThan(0);
});
after(async () => {