From 143c521b3f4fc46cef96ad32c738fcf73c0c3ff4 Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:07:05 +0100 Subject: [PATCH 01/12] [Search] Fix endpoints header persisting beyond search pages (#174627) ## Summary Unmounting the header action was failing in some cases and throwing console errors in all cases. This caused the header component to persist onto pages where it shouldn't be. Instead of explicitly unmounting, which should be done by the parent component of our non-root element, we render an empty element on unmount, which removes the header action in all unmount cases. --- x-pack/plugins/enterprise_search/public/applications/index.tsx | 2 +- .../public/applications/shared/layout/page_template.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 74c81d30a0825..7820ab340b0f9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -199,5 +199,5 @@ export const renderHeaderActions = ( , kibanaHeaderEl ); - return () => ReactDOM.unmountComponentAtNode(kibanaHeaderEl); + return () => ReactDOM.render(<>, kibanaHeaderEl); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx index 2553a12dc17c5..f0cff6a29880e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/page_template.tsx @@ -76,7 +76,7 @@ export const EnterpriseSearchPageTemplateWrapper: React.FC = renderHeaderActions(EndpointsHeaderAction); } return () => { - renderHeaderActions(); + renderHeaderActions(undefined); }; }, []); return ( From fd0e46293ea2bcd3d8e876a896bfe4398acbfa3e Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Wed, 10 Jan 2024 11:16:36 -0500 Subject: [PATCH 02/12] [Response Ops][Alerting] Using `alertsClient` for transform health and anomaly detection jobs health rule types to write default alerts-as-data docs (#174537) Towards https://github.com/elastic/response-ops-team/issues/164 Resolves https://github.com/elastic/kibana/issues/171792 ## Summary * Switches these rule types to use `alertsClient` from alerting framework in favor of the deprecated `alertFactory` * Defines the `default` alert config for these rule types so framework level fields will be written out into the `.alerts-default.alerts-default` index with no rule type specific fields. Example alert doc for transform health rule: ``` { "kibana.alert.reason": "Transform test_transform_01 is not started.", "kibana.alert.rule.category": "Transform health", "kibana.alert.rule.consumer": "alerts", "kibana.alert.rule.execution.uuid": "1dd66818-962e-4fef-8ce2-5a1eab2813a2", "kibana.alert.rule.name": "Test all transforms", "kibana.alert.rule.parameters": { "includeTransforms": [ "*" ], "excludeTransforms": null, "testsConfig": null }, "kibana.alert.rule.producer": "stackAlerts", "kibana.alert.rule.revision": 0, "kibana.alert.rule.rule_type_id": "transform_health", "kibana.alert.rule.tags": [], "kibana.alert.rule.uuid": "7fb57af0-56b5-4c63-9457-add79e9c3e37", "kibana.space_ids": [ "space1" ], "@timestamp": "2024-01-10T14:43:00.974Z", "event.action": "open", "event.kind": "signal", "kibana.alert.action_group": "transform_issue", "kibana.alert.flapping": false, "kibana.alert.flapping_history": [ true ], "kibana.alert.instance.id": "Transform is not started", "kibana.alert.maintenance_window_ids": [], "kibana.alert.status": "active", "kibana.alert.uuid": "25f7b99d-e4ab-4b97-89e4-1a537692ffa5", "kibana.alert.workflow_status": "open", "kibana.alert.duration.us": 0, "kibana.alert.start": "2024-01-10T14:43:00.974Z", "kibana.alert.time_range": { "gte": "2024-01-10T14:43:00.974Z" }, "kibana.version": "8.13.0", "tags": [] } ``` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../alert_as_data_fields.test.ts.snap | 12 ++- .../register_jobs_monitoring_rule_type.ts | 43 ++++++++-- .../register_transform_health_rule_type.ts | 39 +++++++-- x-pack/plugins/transform/tsconfig.json | 4 +- .../transform_health/index.ts | 2 +- .../transform_health/{alert.ts => rule.ts} | 85 +++++++++++++------ 6 files changed, 138 insertions(+), 47 deletions(-) rename x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/{alert.ts => rule.ts} (67%) diff --git a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/alert_as_data_fields.test.ts.snap b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/alert_as_data_fields.test.ts.snap index 3f3785b89f619..95f1fca184d56 100644 --- a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/alert_as_data_fields.test.ts.snap +++ b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/alert_as_data_fields.test.ts.snap @@ -8973,7 +8973,11 @@ Object { } `; -exports[`Alert as data fields checks detect AAD fields changes for: transform_health 1`] = `undefined`; +exports[`Alert as data fields checks detect AAD fields changes for: transform_health 1`] = ` +Object { + "fieldMap": Object {}, +} +`; exports[`Alert as data fields checks detect AAD fields changes for: xpack.ml.anomaly_detection_alert 1`] = ` Object { @@ -9087,7 +9091,11 @@ Object { } `; -exports[`Alert as data fields checks detect AAD fields changes for: xpack.ml.anomaly_detection_jobs_health 1`] = `undefined`; +exports[`Alert as data fields checks detect AAD fields changes for: xpack.ml.anomaly_detection_jobs_health 1`] = ` +Object { + "fieldMap": Object {}, +} +`; exports[`Alert as data fields checks detect AAD fields changes for: xpack.synthetics.alerts.monitorStatus 1`] = ` Object { diff --git a/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts index 2b53fb7a4c7c4..014c3dd365b6d 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_jobs_monitoring_rule_type.ts @@ -16,9 +16,13 @@ import type { ActionGroup, AlertInstanceContext, AlertInstanceState, + RecoveredActionGroupId, RuleTypeState, } from '@kbn/alerting-plugin/common'; +import { AlertsClientError, DEFAULT_AAD_CONFIG } from '@kbn/alerting-plugin/server'; import type { RuleExecutorOptions } from '@kbn/alerting-plugin/server'; +import type { DefaultAlert } from '@kbn/alerts-as-data-utils'; +import { ALERT_REASON } from '@kbn/rule-data-utils'; import { ML_ALERT_TYPES } from '../../../common/constants/alerts'; import { PLUGIN_ID } from '../../../common/constants/app'; import { MINIMUM_FULL_LICENSE } from '../../../common/license'; @@ -90,7 +94,8 @@ export type JobsHealthExecutorOptions = RuleExecutorOptions< Record, Record, AnomalyDetectionJobsHealthAlertContext, - AnomalyDetectionJobRealtimeIssue + AnomalyDetectionJobRealtimeIssue, + DefaultAlert >; export function registerJobsMonitoringRuleType({ @@ -104,7 +109,9 @@ export function registerJobsMonitoringRuleType({ RuleTypeState, AlertInstanceState, AnomalyDetectionJobsHealthAlertContext, - AnomalyDetectionJobRealtimeIssue + AnomalyDetectionJobRealtimeIssue, + RecoveredActionGroupId, + DefaultAlert >({ id: ML_ALERT_TYPES.AD_JOBS_HEALTH, name: i18n.translate('xpack.ml.jobsHealthAlertingRule.name', { @@ -142,12 +149,19 @@ export function registerJobsMonitoringRuleType({ minimumLicenseRequired: MINIMUM_FULL_LICENSE, isExportable: true, doesSetRecoveryContext: true, + alerts: DEFAULT_AAD_CONFIG, async executor(options) { const { services, rule: { name }, } = options; + const { alertsClient } = services; + + if (!alertsClient) { + throw new AlertsClientError(); + } + const fakeRequest = {} as KibanaRequest; const { getTestsResults } = mlServicesProviders.jobsHealthServiceProvider( services.savedObjectsClient, @@ -165,19 +179,30 @@ export function registerJobsMonitoringRuleType({ .join(', ')}` ); - unhealthyTests.forEach(({ name: alertInstanceName, context }) => { - const alertInstance = services.alertFactory.create(alertInstanceName); - alertInstance.scheduleActions(ANOMALY_DETECTION_JOB_REALTIME_ISSUE, context); + unhealthyTests.forEach(({ name: alertName, context }) => { + alertsClient.report({ + id: alertName, + actionGroup: ANOMALY_DETECTION_JOB_REALTIME_ISSUE, + context, + payload: { + [ALERT_REASON]: context.message, + }, + }); }); } // Set context for recovered alerts - const { getRecoveredAlerts } = services.alertFactory.done(); - for (const recoveredAlert of getRecoveredAlerts()) { - const recoveredAlertId = recoveredAlert.getId(); + for (const recoveredAlert of alertsClient.getRecoveredAlerts()) { + const recoveredAlertId = recoveredAlert.alert.getId(); const testResult = executionResult.find((v) => v.name === recoveredAlertId); if (testResult) { - recoveredAlert.setContext(testResult.context); + alertsClient.setAlertData({ + id: recoveredAlertId, + context: testResult.context, + payload: { + [ALERT_REASON]: testResult.context.message, + }, + }); } } diff --git a/x-pack/plugins/transform/server/lib/alerting/transform_health_rule_type/register_transform_health_rule_type.ts b/x-pack/plugins/transform/server/lib/alerting/transform_health_rule_type/register_transform_health_rule_type.ts index 3eaa1db46018d..c6450fc243dc2 100644 --- a/x-pack/plugins/transform/server/lib/alerting/transform_health_rule_type/register_transform_health_rule_type.ts +++ b/x-pack/plugins/transform/server/lib/alerting/transform_health_rule_type/register_transform_health_rule_type.ts @@ -11,11 +11,14 @@ import type { ActionGroup, AlertInstanceContext, AlertInstanceState, + RecoveredActionGroupId, RuleTypeState, } from '@kbn/alerting-plugin/common'; -import type { RuleType } from '@kbn/alerting-plugin/server'; +import { AlertsClientError, DEFAULT_AAD_CONFIG, RuleType } from '@kbn/alerting-plugin/server'; import type { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/server'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; +import type { DefaultAlert } from '@kbn/alerts-as-data-utils'; +import { ALERT_REASON } from '@kbn/rule-data-utils'; import { PLUGIN, type TransformHealth, TRANSFORM_RULE_TYPE } from '../../../../common/constants'; import { transformHealthRuleParams, TransformHealthRuleParams } from './schema'; import { transformHealthServiceProvider } from './transform_health_service'; @@ -73,7 +76,9 @@ export function getTransformHealthRuleType( RuleTypeState, AlertInstanceState, TransformHealthAlertContext, - TransformIssue + TransformIssue, + RecoveredActionGroupId, + DefaultAlert > { return { id: TRANSFORM_RULE_TYPE.TRANSFORM_HEALTH, @@ -110,12 +115,17 @@ export function getTransformHealthRuleType( minimumLicenseRequired: PLUGIN.MINIMUM_LICENSE_REQUIRED, isExportable: true, doesSetRecoveryContext: true, + alerts: DEFAULT_AAD_CONFIG, async executor(options) { const { - services: { scopedClusterClient, alertFactory, uiSettingsClient }, + services: { scopedClusterClient, alertsClient, uiSettingsClient }, params, } = options; + if (!alertsClient) { + throw new AlertsClientError(); + } + const fieldFormatsRegistry = await getFieldFormatsStart().fieldFormatServiceFactory( uiSettingsClient ); @@ -131,18 +141,29 @@ export function getTransformHealthRuleType( if (unhealthyTests.length > 0) { unhealthyTests.forEach(({ name: alertInstanceName, context }) => { - const alertInstance = alertFactory.create(alertInstanceName); - alertInstance.scheduleActions(TRANSFORM_ISSUE, context); + alertsClient.report({ + id: alertInstanceName, + actionGroup: TRANSFORM_ISSUE, + context, + payload: { + [ALERT_REASON]: context.message, + }, + }); }); } // Set context for recovered alerts - const { getRecoveredAlerts } = alertFactory.done(); - for (const recoveredAlert of getRecoveredAlerts()) { - const recoveredAlertId = recoveredAlert.getId(); + for (const recoveredAlert of alertsClient.getRecoveredAlerts()) { + const recoveredAlertId = recoveredAlert.alert.getId(); const testResult = executionResult.find((v) => v.name === recoveredAlertId); if (testResult) { - recoveredAlert.setContext(testResult.context); + alertsClient.setAlertData({ + id: recoveredAlertId, + context: testResult.context, + payload: { + [ALERT_REASON]: testResult.context.message, + }, + }); } } diff --git a/x-pack/plugins/transform/tsconfig.json b/x-pack/plugins/transform/tsconfig.json index 41f6fa29b8aa0..faa4e2e67dd53 100644 --- a/x-pack/plugins/transform/tsconfig.json +++ b/x-pack/plugins/transform/tsconfig.json @@ -72,7 +72,9 @@ "@kbn/data-view-editor-plugin", "@kbn/ml-data-view-utils", "@kbn/ml-creation-wizard-utils", - "@kbn/code-editor" + "@kbn/alerts-as-data-utils", + "@kbn/code-editor", + "@kbn/rule-data-utils" ], "exclude": [ "target/**/*", diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/index.ts index adc93332a0f56..3cb706576efc3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/index.ts @@ -10,6 +10,6 @@ import { FtrProviderContext } from '../../../../../../common/ftr_provider_contex // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { describe('transform_health', function () { - loadTestFile(require.resolve('./alert')); + loadTestFile(require.resolve('./rule')); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/rule.ts similarity index 67% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/alert.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/rule.ts index 046ad43ef1514..24efdd2613591 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/rule.ts @@ -8,19 +8,30 @@ import expect from '@kbn/expect'; import { PutTransformsRequestSchema } from '@kbn/transform-plugin/common/api_schemas/transforms'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; +import { + ALERT_ACTION_GROUP, + ALERT_INSTANCE_ID, + ALERT_REASON, + ALERT_RULE_CATEGORY, + ALERT_RULE_NAME, + ALERT_RULE_TYPE_ID, + ALERT_RULE_UUID, + ALERT_STATUS, + EVENT_ACTION, +} from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; import { getUrlPrefix, ObjectRemover } from '../../../../../../common/lib'; import { Spaces } from '../../../../../scenarios'; -const ACTION_TYPE_ID = '.index'; -const ALERT_TYPE_ID = 'transform_health'; +const CONNECTOR_TYPE_ID = '.index'; +const RULE_TYPE_ID = 'transform_health'; const ES_TEST_INDEX_SOURCE = 'transform-alert:transform-health'; const ES_TEST_INDEX_REFERENCE = '-na-'; const ES_TEST_OUTPUT_INDEX_NAME = `${ES_TEST_INDEX_NAME}-ts-output`; -const ALERT_INTERVAL_SECONDS = 3; +const RULE_INTERVAL_SECONDS = 3; -interface CreateAlertParams { +interface CreateRuleParams { name: string; includeTransforms: string[]; excludeTransforms?: string[] | null; @@ -52,7 +63,7 @@ export function generateTransformConfig(transformId: string): PutTransformsReque } // eslint-disable-next-line import/no-default-export -export default function alertTests({ getService }: FtrProviderContext) { +export default function ruleTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const retry = getService('retry'); @@ -62,10 +73,15 @@ export default function alertTests({ getService }: FtrProviderContext) { const esTestIndexTool = new ESTestIndexTool(es, retry); const esTestIndexToolOutput = new ESTestIndexTool(es, retry, ES_TEST_OUTPUT_INDEX_NAME); + const esTestIndexToolAAD = new ESTestIndexTool( + es, + retry, + `.internal.alerts-default.alerts-default-000001` + ); - describe('alert', async () => { + describe('rule', async () => { const objectRemover = new ObjectRemover(supertest); - let actionId: string; + let connectorId: string; const transformId = 'test_transform_01'; const destinationIndex = generateDestIndex(transformId); @@ -76,10 +92,12 @@ export default function alertTests({ getService }: FtrProviderContext) { await esTestIndexToolOutput.destroy(); await esTestIndexToolOutput.setup(); + await esTestIndexToolAAD.removeAll(); + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); await transform.testResources.setKibanaTimeZoneToUTC(); - actionId = await createAction(); + connectorId = await createConnector(); await transform.api.createIndices(destinationIndex); await createTransform(transformId); @@ -89,18 +107,19 @@ export default function alertTests({ getService }: FtrProviderContext) { await objectRemover.removeAll(); await esTestIndexTool.destroy(); await esTestIndexToolOutput.destroy(); + await esTestIndexToolAAD.removeAll(); await transform.api.cleanTransformIndices(); }); it('runs correctly', async () => { - await createAlert({ + const ruleId = await createRule({ name: 'Test all transforms', includeTransforms: ['*'], }); await stopTransform(transformId); - log.debug('Checking created alert instances...'); + log.debug('Checking created alerts...'); const docs = await waitForDocs(1); for (const doc of docs) { @@ -109,6 +128,18 @@ export default function alertTests({ getService }: FtrProviderContext) { expect(name).to.be('Test all transforms'); expect(message).to.be('Transform test_transform_01 is not started.'); } + + const aadDocs = await getAllAADDocs(1); + const alertDoc = aadDocs.body.hits.hits[0]._source; + expect(alertDoc[ALERT_REASON]).to.be(`Transform test_transform_01 is not started.`); + expect(alertDoc[ALERT_RULE_CATEGORY]).to.be(`Transform health`); + expect(alertDoc[ALERT_RULE_NAME]).to.be(`Test all transforms`); + expect(alertDoc[ALERT_RULE_TYPE_ID]).to.be(`transform_health`); + expect(alertDoc[ALERT_RULE_UUID]).to.be(ruleId); + expect(alertDoc[EVENT_ACTION]).to.be(`open`); + expect(alertDoc[ALERT_ACTION_GROUP]).to.be(`transform_issue`); + expect(alertDoc[ALERT_INSTANCE_ID]).to.be(`Transform is not started`); + expect(alertDoc[ALERT_STATUS]).to.be(`active`); }); async function waitForDocs(count: number): Promise { @@ -119,15 +150,19 @@ export default function alertTests({ getService }: FtrProviderContext) { ); } + async function getAllAADDocs(count: number): Promise { + return await esTestIndexToolAAD.getAll(count); + } + async function createTransform(id: string) { const config = generateTransformConfig(id); await transform.api.createAndRunTransform(id, config); } - async function createAlert(params: CreateAlertParams): Promise { + async function createRule(params: CreateRuleParams): Promise { log.debug(`Creating an alerting rule "${params.name}"...`); const action = { - id: actionId, + id: connectorId, group: 'transform_issue', params: { documents: [ @@ -143,15 +178,15 @@ export default function alertTests({ getService }: FtrProviderContext) { }, }; - const { status, body: createdAlert } = await supertest + const { status, body: createdRule } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') .send({ name: params.name, consumer: 'alerts', enabled: true, - rule_type_id: ALERT_TYPE_ID, - schedule: { interval: `${ALERT_INTERVAL_SECONDS}s` }, + rule_type_id: RULE_TYPE_ID, + schedule: { interval: `${RULE_INTERVAL_SECONDS}s` }, actions: [action], notify_when: 'onActiveAlert', params: { @@ -160,29 +195,29 @@ export default function alertTests({ getService }: FtrProviderContext) { }); // will print the error body, if an error occurred - // if (statusCode !== 200) console.log(createdAlert); + // if (statusCode !== 200) console.log(createdRule); expect(status).to.be(200); - const alertId = createdAlert.id; - objectRemover.add(Spaces.space1.id, alertId, 'rule', 'alerting'); + const ruleId = createdRule.id; + objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting'); - return alertId; + return ruleId; } async function stopTransform(id: string) { await transform.api.stopTransform(id); } - async function createAction(): Promise { - log.debug('Creating an action...'); + async function createConnector(): Promise { + log.debug('Creating a connector...'); // @ts-ignore - const { statusCode, body: createdAction } = await supertest + const { statusCode, body: createdConnector } = await supertest .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') .send({ name: 'index action for transform health FT', - connector_type_id: ACTION_TYPE_ID, + connector_type_id: CONNECTOR_TYPE_ID, config: { index: ES_TEST_OUTPUT_INDEX_NAME, }, @@ -191,9 +226,9 @@ export default function alertTests({ getService }: FtrProviderContext) { expect(statusCode).to.be(200); - log.debug(`Action with id "${createdAction.id}" has been created.`); + log.debug(`Connector with id "${createdConnector.id}" has been created.`); - const resultId = createdAction.id; + const resultId = createdConnector.id; objectRemover.add(Spaces.space1.id, resultId, 'connector', 'actions'); return resultId; From e78b8e5579f1b8aa147a74f38503a8d712860c88 Mon Sep 17 00:00:00 2001 From: Ioana Tagirta Date: Wed, 10 Jan 2024 17:17:34 +0100 Subject: [PATCH 03/12] [Search Relevance] Remove E5 Multilingual Callout (#174589) ## Summary Issue: SEARCH-210 Removes the E5 calllout since it's no longer needed. The PR is easier to review by commit, the one change I kept from https://github.com/elastic/kibana/pull/171887 is the ELSER description that we want to keep. --- .../create_e5_multilingual_model_api_logic.ts | 35 -- .../fetch_e5_multilingual_model_api_logic.ts | 37 -- .../start_e5_multilingual_model_api_logic.ts | 35 -- .../deploy_model.test.tsx | 81 --- .../e5_multilingual_callout/deploy_model.tsx | 120 ----- .../e5_multilingual_callout.test.tsx | 77 --- .../e5_multilingual_callout.tsx | 150 ------ .../e5_multilingual_callout_logic.test.ts | 469 ------------------ .../e5_multilingual_callout_logic.ts | 308 ------------ .../e5_multilingual_errors.test.tsx | 34 -- .../e5_multilingual_errors.tsx | 37 -- .../model_deployed.test.tsx | 81 --- .../model_deployed.tsx | 113 ----- .../model_deployment_in_progress.test.tsx | 39 -- .../model_deployment_in_progress.tsx | 58 --- .../model_started.test.tsx | 57 --- .../e5_multilingual_callout/model_started.tsx | 135 ----- .../text_expansion_callout/deploy_model.tsx | 2 +- 18 files changed, 1 insertion(+), 1867 deletions(-) delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/create_e5_multilingual_model_api_logic.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/fetch_e5_multilingual_model_api_logic.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/start_e5_multilingual_model_api_logic.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/deploy_model.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/deploy_model.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout_logic.test.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout_logic.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_errors.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_errors.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployed.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployed.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployment_in_progress.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployment_in_progress.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_started.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_started.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/create_e5_multilingual_model_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/create_e5_multilingual_model_api_logic.ts deleted file mode 100644 index e4b50c70bfc84..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/create_e5_multilingual_model_api_logic.ts +++ /dev/null @@ -1,35 +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 { Actions, createApiLogic } from '../../../../shared/api_logic/create_api_logic'; -import { HttpLogic } from '../../../../shared/http'; - -export interface CreateE5MultilingualModelArgs { - modelId: string; -} - -export interface CreateE5MultilingualModelResponse { - deploymentState: string; - modelId: string; -} - -export const createE5MultilingualModel = async ({ - modelId, -}: CreateE5MultilingualModelArgs): Promise => { - const route = `/internal/enterprise_search/ml/models/${modelId}`; - return await HttpLogic.values.http.post(route); -}; - -export const CreateE5MultilingualModelApiLogic = createApiLogic( - ['create_e5_multilingual_model_api_logic'], - createE5MultilingualModel, - { showErrorFlash: false } -); - -export type CreateE5MultilingualModelApiLogicActions = Actions< - CreateE5MultilingualModelArgs, - CreateE5MultilingualModelResponse ->; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/fetch_e5_multilingual_model_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/fetch_e5_multilingual_model_api_logic.ts deleted file mode 100644 index 88918a56fb331..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/fetch_e5_multilingual_model_api_logic.ts +++ /dev/null @@ -1,37 +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 { Actions, createApiLogic } from '../../../../shared/api_logic/create_api_logic'; -import { HttpLogic } from '../../../../shared/http'; - -export interface FetchE5MultilingualModelArgs { - modelId: string; -} - -export interface FetchE5MultilingualModelResponse { - deploymentState: string; - modelId: string; - targetAllocationCount: number; - nodeAllocationCount: number; - threadsPerAllocation: number; -} - -export const fetchE5MultilingualModelStatus = async ({ modelId }: FetchE5MultilingualModelArgs) => { - return await HttpLogic.values.http.get( - `/internal/enterprise_search/ml/models/${modelId}` - ); -}; - -export const FetchE5MultilingualModelApiLogic = createApiLogic( - ['fetch_e5_multilingual_model_api_logic'], - fetchE5MultilingualModelStatus, - { showErrorFlash: false } -); - -export type FetchE5MultilingualModelApiLogicActions = Actions< - FetchE5MultilingualModelArgs, - FetchE5MultilingualModelResponse ->; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/start_e5_multilingual_model_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/start_e5_multilingual_model_api_logic.ts deleted file mode 100644 index 9b0e1520da710..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/e5_multilingual/start_e5_multilingual_model_api_logic.ts +++ /dev/null @@ -1,35 +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 { Actions, createApiLogic } from '../../../../shared/api_logic/create_api_logic'; -import { HttpLogic } from '../../../../shared/http'; - -export interface StartE5MultilingualModelArgs { - modelId: string; -} - -export interface StartE5MultilingualModelResponse { - deploymentState: string; - modelId: string; -} - -export const startE5MultilingualModel = async ({ - modelId, -}: StartE5MultilingualModelArgs): Promise => { - const route = `/internal/enterprise_search/ml/models/${modelId}/deploy`; - return await HttpLogic.values.http.post(route); -}; - -export const StartE5MultilingualModelApiLogic = createApiLogic( - ['start_e5_multilingual_model_api_logic'], - startE5MultilingualModel, - { showErrorFlash: false } -); - -export type StartE5MultilingualModelApiLogicActions = Actions< - StartE5MultilingualModelArgs, - StartE5MultilingualModelResponse ->; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/deploy_model.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/deploy_model.test.tsx deleted file mode 100644 index 739e709ea10f0..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/deploy_model.test.tsx +++ /dev/null @@ -1,81 +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 { setMockValues } from '../../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { DeployModel } from './deploy_model'; -import { E5MultilingualDismissButton } from './e5_multilingual_callout'; - -const DEFAULT_VALUES = { - startE5MultilingualModelError: undefined, - isCreateButtonDisabled: false, - isModelDownloadInProgress: false, - isModelDownloaded: false, - isModelStarted: false, - isStartButtonDisabled: false, -}; - -describe('DeployModel', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(DEFAULT_VALUES); - }); - it('renders deploy button', () => { - const wrapper = shallow( - {}} - ingestionMethod="crawler" - isCreateButtonDisabled={false} - isDismissable={false} - /> - ); - expect(wrapper.find(EuiButton).length).toBe(1); - const button = wrapper.find(EuiButton); - expect(button.prop('disabled')).toBe(false); - }); - it('renders disabled deploy button if it is set to disabled', () => { - const wrapper = shallow( - {}} - ingestionMethod="crawler" - isCreateButtonDisabled - isDismissable={false} - /> - ); - expect(wrapper.find(EuiButton).length).toBe(1); - const button = wrapper.find(EuiButton); - expect(button.prop('disabled')).toBe(true); - }); - it('renders dismiss button if it is set to dismissable', () => { - const wrapper = shallow( - {}} - ingestionMethod="crawler" - isCreateButtonDisabled={false} - isDismissable - /> - ); - expect(wrapper.find(E5MultilingualDismissButton).length).toBe(1); - }); - it('does not render dismiss button if it is set to non-dismissable', () => { - const wrapper = shallow( - {}} - ingestionMethod="crawler" - isCreateButtonDisabled={false} - isDismissable={false} - /> - ); - expect(wrapper.find(E5MultilingualDismissButton).length).toBe(0); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/deploy_model.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/deploy_model.tsx deleted file mode 100644 index 6d616a453e9f7..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/deploy_model.tsx +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { useActions } from 'kea'; - -import { - EuiBadge, - EuiButton, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedHTMLMessage, FormattedMessage } from '@kbn/i18n-react'; - -import { E5MultilingualCallOutState, E5MultilingualDismissButton } from './e5_multilingual_callout'; -import { E5MultilingualCalloutLogic } from './e5_multilingual_callout_logic'; - -export const DeployModel = ({ - dismiss, - ingestionMethod, - isCreateButtonDisabled, - isDismissable, -}: Pick< - E5MultilingualCallOutState, - 'dismiss' | 'ingestionMethod' | 'isCreateButtonDisabled' | 'isDismissable' ->) => { - const { createE5MultilingualModel } = useActions(E5MultilingualCalloutLogic); - - return ( - - - - - - - - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.title', - { defaultMessage: 'Improve your results with E5' } - )} -

-
-
- {isDismissable && ( - - - - )} -
-
- - - - - - - - - - - createE5MultilingualModel()} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.e5MultilingualCallOut.deployButton.label', - { - defaultMessage: 'Deploy', - } - )} - - - - - - - - - - - -
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout.test.tsx deleted file mode 100644 index ad9f99960ad04..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout.test.tsx +++ /dev/null @@ -1,77 +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 { setMockValues } from '../../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { DeployModel } from './deploy_model'; -import { E5MultilingualCallOut } from './e5_multilingual_callout'; -import { E5MultilingualErrors } from './e5_multilingual_errors'; -import { ModelDeployed } from './model_deployed'; -import { ModelDeploymentInProgress } from './model_deployment_in_progress'; -import { ModelStarted } from './model_started'; - -const DEFAULT_VALUES = { - isCreateButtonDisabled: false, - isModelDownloadInProgress: false, - isModelDownloaded: false, - isModelStarted: false, - isStartButtonDisabled: false, -}; - -describe('E5MultilingualCallOut', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(DEFAULT_VALUES); - }); - it('renders error panel instead of normal panel if there are some errors', () => { - setMockValues({ - ...DEFAULT_VALUES, - e5MultilingualError: { - title: 'Error with E5 Multilingual deployment', - message: 'Mocked error message', - }, - }); - - const wrapper = shallow(); - expect(wrapper.find(E5MultilingualErrors).length).toBe(1); - }); - it('renders panel with deployment instructions if the model is not deployed', () => { - const wrapper = shallow(); - expect(wrapper.find(DeployModel).length).toBe(1); - }); - it('renders panel with deployment in progress status if the model is being deployed', () => { - setMockValues({ - ...DEFAULT_VALUES, - isModelDownloadInProgress: true, - }); - - const wrapper = shallow(); - expect(wrapper.find(ModelDeploymentInProgress).length).toBe(1); - }); - it('renders panel with deployment in progress status if the model has been deployed', () => { - setMockValues({ - ...DEFAULT_VALUES, - isModelDownloaded: true, - }); - - const wrapper = shallow(); - expect(wrapper.find(ModelDeployed).length).toBe(1); - }); - it('renders panel with deployment in progress status if the model has been started', () => { - setMockValues({ - ...DEFAULT_VALUES, - isModelStarted: true, - }); - - const wrapper = shallow(); - expect(wrapper.find(ModelStarted).length).toBe(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout.tsx deleted file mode 100644 index bfe92e5b7c96e..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout.tsx +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { useCallback, useState } from 'react'; - -import { useValues } from 'kea'; - -import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { KibanaLogic } from '../../../../../../shared/kibana'; - -import { useLocalStorage } from '../../../../../../shared/use_local_storage'; - -import { IndexViewLogic } from '../../../index_view_logic'; - -import { TRAINED_MODELS_PATH } from '../utils'; - -import { DeployModel } from './deploy_model'; -import { E5MultilingualCalloutLogic } from './e5_multilingual_callout_logic'; -import { E5MultilingualErrors } from './e5_multilingual_errors'; -import { ModelDeployed } from './model_deployed'; -import { ModelDeploymentInProgress } from './model_deployment_in_progress'; -import { ModelStarted } from './model_started'; - -export interface E5MultilingualCallOutState { - dismiss: () => void; - ingestionMethod: string; - isCompact: boolean; - isCreateButtonDisabled: boolean; - isDismissable: boolean; - isSingleThreaded: boolean; - isStartButtonDisabled: boolean; - show: boolean; -} - -export interface E5MultilingualCallOutProps { - isCompact?: boolean; - isDismissable?: boolean; -} - -export const E5_MULTILINGUAL_CALL_OUT_DISMISSED_KEY = - 'enterprise-search-e5-multilingual-callout-dismissed'; - -export const E5MultilingualDismissButton = ({ - dismiss, -}: Pick) => { - return ( - - ); -}; - -export const FineTuneModelsButton: React.FC = () => ( - - KibanaLogic.values.navigateToUrl(TRAINED_MODELS_PATH, { - shouldNotCreateHref: true, - }) - } - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.e5MultilingualCallOut.fineTuneModelButton', - { - defaultMessage: 'Fine-tune performance', - } - )} - -); - -export const E5MultilingualCallOut: React.FC = (props) => { - const isCompact = props.isCompact !== undefined ? props.isCompact : false; - const isDismissable = props.isDismissable !== undefined ? props.isDismissable : false; - - const [calloutDismissed, setCalloutDismissed] = useLocalStorage( - E5_MULTILINGUAL_CALL_OUT_DISMISSED_KEY, - false - ); - - const [show, setShow] = useState(() => { - if (!isDismissable) return true; - return !calloutDismissed; - }); - - const dismiss = useCallback(() => { - setShow(false); - setCalloutDismissed(true); - }, []); - - const { ingestionMethod } = useValues(IndexViewLogic); - const { - isCreateButtonDisabled, - isModelDownloadInProgress, - isModelDownloaded, - isModelRunningSingleThreaded, - isModelStarted, - e5MultilingualError, - isStartButtonDisabled, - } = useValues(E5MultilingualCalloutLogic); - - if (e5MultilingualError) return ; - - if (!show) return null; - - if (isModelDownloadInProgress) { - return ; - } else if (isModelDownloaded) { - return ( - - ); - } else if (isModelStarted) { - return ( - - ); - } - - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout_logic.test.ts deleted file mode 100644 index 0ea1d546d407f..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout_logic.test.ts +++ /dev/null @@ -1,469 +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 { LogicMounter } from '../../../../../../__mocks__/kea_logic'; - -import { HttpResponse } from '@kbn/core/public'; - -import { ErrorResponse, HttpError, Status } from '../../../../../../../../common/types/api'; -import { MlModelDeploymentState } from '../../../../../../../../common/types/ml'; -import { CreateE5MultilingualModelApiLogic } from '../../../../../api/ml_models/e5_multilingual/create_e5_multilingual_model_api_logic'; -import { FetchE5MultilingualModelApiLogic } from '../../../../../api/ml_models/e5_multilingual/fetch_e5_multilingual_model_api_logic'; -import { StartE5MultilingualModelApiLogic } from '../../../../../api/ml_models/e5_multilingual/start_e5_multilingual_model_api_logic'; - -import { - E5MultilingualCalloutLogic, - E5MultilingualCalloutValues, - getE5MultilingualError, -} from './e5_multilingual_callout_logic'; - -const DEFAULT_VALUES: E5MultilingualCalloutValues = { - createE5MultilingualModelError: undefined, - createE5MultilingualModelStatus: Status.IDLE, - createdE5MultilingualModel: undefined, - fetchE5MultilingualModelError: undefined, - isCreateButtonDisabled: false, - isModelDownloadInProgress: false, - isModelDownloaded: false, - isModelRunningSingleThreaded: false, - isModelStarted: false, - isPollingE5MultilingualModelActive: false, - isStartButtonDisabled: false, - startE5MultilingualModelError: undefined, - startE5MultilingualModelStatus: Status.IDLE, - e5MultilingualModel: undefined, - e5MultilingualModelPollTimeoutId: null, - e5MultilingualError: null, -}; - -jest.useFakeTimers(); - -describe('E5MultilingualCalloutLogic', () => { - const { mount } = new LogicMounter(E5MultilingualCalloutLogic); - const { mount: mountCreateE5MultilingualModelApiLogic } = new LogicMounter( - CreateE5MultilingualModelApiLogic - ); - const { mount: mountFetchE5MultilingualModelApiLogic } = new LogicMounter( - FetchE5MultilingualModelApiLogic - ); - const { mount: mountStartE5MultilingualModelApiLogic } = new LogicMounter( - StartE5MultilingualModelApiLogic - ); - - beforeEach(() => { - jest.clearAllMocks(); - mountCreateE5MultilingualModelApiLogic(); - mountFetchE5MultilingualModelApiLogic(); - mountStartE5MultilingualModelApiLogic(); - mount(); - }); - - it('has expected default values', () => { - expect(E5MultilingualCalloutLogic.values).toEqual(DEFAULT_VALUES); - }); - - describe('getE5MultilingualError', () => { - const error = { - body: { - error: 'some-error', - message: 'some-error-message', - statusCode: 500, - }, - } as HttpError; - it('returns null if there is no error', () => { - expect(getE5MultilingualError(undefined, undefined, undefined)).toBe(null); - }); - it('uses the correct title and message from a create error', () => { - expect(getE5MultilingualError(error, undefined, undefined)).toEqual({ - title: 'Error with E5 Multilingual deployment', - message: error.body?.message, - }); - }); - it('uses the correct title and message from a fetch error', () => { - expect(getE5MultilingualError(undefined, error, undefined)).toEqual({ - title: 'Error fetching E5 Multilingual model', - message: error.body?.message, - }); - }); - it('uses the correct title and message from a start error', () => { - expect(getE5MultilingualError(undefined, undefined, error)).toEqual({ - title: 'Error starting E5 Multilingual deployment', - message: error.body?.message, - }); - }); - }); - - describe('listeners', () => { - describe('createE5MultilingualModelPollingTimeout', () => { - const duration = 5000; - it('sets polling timeout', () => { - jest.spyOn(global, 'setTimeout'); - jest.spyOn(E5MultilingualCalloutLogic.actions, 'setE5MultilingualModelPollingId'); - - E5MultilingualCalloutLogic.actions.createE5MultilingualModelPollingTimeout(duration); - - expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), duration); - expect( - E5MultilingualCalloutLogic.actions.setE5MultilingualModelPollingId - ).toHaveBeenCalled(); - }); - it('clears polling timeout if it is set', () => { - mount({ - ...DEFAULT_VALUES, - e5MultilingualModelPollTimeoutId: 'timeout-id', - }); - - jest.spyOn(global, 'clearTimeout'); - - E5MultilingualCalloutLogic.actions.createE5MultilingualModelPollingTimeout(duration); - - expect(clearTimeout).toHaveBeenCalledWith('timeout-id'); - }); - }); - - describe('createE5MultilingualModelSuccess', () => { - it('sets createdE5MultilingualModel', () => { - jest.spyOn(E5MultilingualCalloutLogic.actions, 'fetchE5MultilingualModel'); - jest.spyOn(E5MultilingualCalloutLogic.actions, 'startPollingE5MultilingualModel'); - - E5MultilingualCalloutLogic.actions.createE5MultilingualModelSuccess({ - deploymentState: MlModelDeploymentState.Downloading, - modelId: 'mock-model-id', - }); - - expect(E5MultilingualCalloutLogic.actions.fetchE5MultilingualModel).toHaveBeenCalled(); - expect( - E5MultilingualCalloutLogic.actions.startPollingE5MultilingualModel - ).toHaveBeenCalled(); - }); - }); - - describe('fetchE5MultilingualModelSuccess', () => { - const data = { - deploymentState: MlModelDeploymentState.Downloading, - modelId: 'mock-model-id', - targetAllocationCount: 1, - nodeAllocationCount: 1, - threadsPerAllocation: 1, - }; - - it('starts polling when the model is downloading and polling is not active', () => { - mount({ - ...DEFAULT_VALUES, - }); - jest.spyOn(E5MultilingualCalloutLogic.actions, 'startPollingE5MultilingualModel'); - - E5MultilingualCalloutLogic.actions.fetchE5MultilingualModelSuccess(data); - - expect( - E5MultilingualCalloutLogic.actions.startPollingE5MultilingualModel - ).toHaveBeenCalled(); - }); - it('sets polling timeout when the model is downloading and polling is active', () => { - mount({ - ...DEFAULT_VALUES, - e5MultilingualModelPollTimeoutId: 'timeout-id', - }); - jest.spyOn(E5MultilingualCalloutLogic.actions, 'createE5MultilingualModelPollingTimeout'); - - E5MultilingualCalloutLogic.actions.fetchE5MultilingualModelSuccess(data); - - expect( - E5MultilingualCalloutLogic.actions.createE5MultilingualModelPollingTimeout - ).toHaveBeenCalled(); - }); - it('stops polling when the model is downloaded and polling is active', () => { - mount({ - ...DEFAULT_VALUES, - e5MultilingualModelPollTimeoutId: 'timeout-id', - }); - jest.spyOn(E5MultilingualCalloutLogic.actions, 'stopPollingE5MultilingualModel'); - - E5MultilingualCalloutLogic.actions.fetchE5MultilingualModelSuccess({ - deploymentState: MlModelDeploymentState.Downloaded, - modelId: 'mock-model-id', - targetAllocationCount: 1, - nodeAllocationCount: 1, - threadsPerAllocation: 1, - }); - - expect( - E5MultilingualCalloutLogic.actions.stopPollingE5MultilingualModel - ).toHaveBeenCalled(); - }); - }); - - describe('fetchE5MultilingualModelError', () => { - it('stops polling if it is active', () => { - mount({ - ...DEFAULT_VALUES, - e5MultilingualModelPollTimeoutId: 'timeout-id', - }); - jest.spyOn(E5MultilingualCalloutLogic.actions, 'createE5MultilingualModelPollingTimeout'); - - E5MultilingualCalloutLogic.actions.fetchE5MultilingualModelError({ - body: { - error: '', - message: 'some error', - statusCode: 500, - }, - } as HttpResponse); - - expect( - E5MultilingualCalloutLogic.actions.createE5MultilingualModelPollingTimeout - ).toHaveBeenCalled(); - }); - }); - - describe('startPollingE5MultilingualModel', () => { - it('sets polling timeout', () => { - jest.spyOn(E5MultilingualCalloutLogic.actions, 'createE5MultilingualModelPollingTimeout'); - - E5MultilingualCalloutLogic.actions.startPollingE5MultilingualModel(); - - expect( - E5MultilingualCalloutLogic.actions.createE5MultilingualModelPollingTimeout - ).toHaveBeenCalled(); - }); - it('clears polling timeout if it is set', () => { - mount({ - ...DEFAULT_VALUES, - e5MultilingualModelPollTimeoutId: 'timeout-id', - }); - - jest.spyOn(global, 'clearTimeout'); - - E5MultilingualCalloutLogic.actions.startPollingE5MultilingualModel(); - - expect(clearTimeout).toHaveBeenCalledWith('timeout-id'); - }); - }); - - describe('startE5MultilingualModelSuccess', () => { - it('sets startedE5MultilingualModel', () => { - jest.spyOn(E5MultilingualCalloutLogic.actions, 'fetchE5MultilingualModel'); - - E5MultilingualCalloutLogic.actions.startE5MultilingualModelSuccess({ - deploymentState: MlModelDeploymentState.FullyAllocated, - modelId: 'mock-model-id', - }); - - expect(E5MultilingualCalloutLogic.actions.fetchE5MultilingualModel).toHaveBeenCalled(); - }); - }); - - describe('stopPollingE5MultilingualModel', () => { - it('clears polling timeout and poll timeout ID if it is set', () => { - mount({ - ...DEFAULT_VALUES, - e5MultilingualModelPollTimeoutId: 'timeout-id', - }); - - jest.spyOn(global, 'clearTimeout'); - jest.spyOn(E5MultilingualCalloutLogic.actions, 'clearE5MultilingualModelPollingId'); - - E5MultilingualCalloutLogic.actions.stopPollingE5MultilingualModel(); - - expect(clearTimeout).toHaveBeenCalledWith('timeout-id'); - expect( - E5MultilingualCalloutLogic.actions.clearE5MultilingualModelPollingId - ).toHaveBeenCalled(); - }); - }); - }); - - describe('reducers', () => { - describe('e5MultilingualModelPollTimeoutId', () => { - it('gets cleared on clearE5MultilingualModelPollingId', () => { - E5MultilingualCalloutLogic.actions.clearE5MultilingualModelPollingId(); - - expect(E5MultilingualCalloutLogic.values.e5MultilingualModelPollTimeoutId).toBe(null); - }); - it('gets set on setE5MultilingualModelPollingId', () => { - const timeout = setTimeout(() => {}, 500); - E5MultilingualCalloutLogic.actions.setE5MultilingualModelPollingId(timeout); - - expect(E5MultilingualCalloutLogic.values.e5MultilingualModelPollTimeoutId).toEqual(timeout); - }); - }); - }); - - describe('selectors', () => { - describe('isCreateButtonDisabled', () => { - it('is set to false if the fetch model API is idle', () => { - CreateE5MultilingualModelApiLogic.actions.apiReset(); - expect(E5MultilingualCalloutLogic.values.isCreateButtonDisabled).toBe(false); - }); - it('is set to true if the fetch model API is not idle', () => { - CreateE5MultilingualModelApiLogic.actions.apiSuccess({ - deploymentState: MlModelDeploymentState.Downloading, - modelId: 'mock-model-id', - }); - expect(E5MultilingualCalloutLogic.values.isCreateButtonDisabled).toBe(true); - }); - }); - - describe('e5MultilingualError', () => { - const error = { - body: { - error: 'Error with E5 Multilingual deployment', - message: 'Mocked error message', - statusCode: 500, - }, - } as HttpError; - - it('returns null when there are no errors', () => { - CreateE5MultilingualModelApiLogic.actions.apiReset(); - FetchE5MultilingualModelApiLogic.actions.apiReset(); - StartE5MultilingualModelApiLogic.actions.apiReset(); - expect(E5MultilingualCalloutLogic.values.e5MultilingualError).toBe(null); - }); - it('returns extracted error for create', () => { - CreateE5MultilingualModelApiLogic.actions.apiError(error); - expect(E5MultilingualCalloutLogic.values.e5MultilingualError).toStrictEqual({ - title: 'Error with E5 Multilingual deployment', - message: 'Mocked error message', - }); - }); - it('returns extracted error for fetch', () => { - FetchE5MultilingualModelApiLogic.actions.apiError(error); - expect(E5MultilingualCalloutLogic.values.e5MultilingualError).toStrictEqual({ - title: 'Error fetching E5 Multilingual model', - message: 'Mocked error message', - }); - }); - it('returns extracted error for start', () => { - StartE5MultilingualModelApiLogic.actions.apiError(error); - expect(E5MultilingualCalloutLogic.values.e5MultilingualError).toStrictEqual({ - title: 'Error starting E5 Multilingual deployment', - message: 'Mocked error message', - }); - }); - }); - - describe('isModelDownloadInProgress', () => { - it('is set to true if the model is downloading', () => { - FetchE5MultilingualModelApiLogic.actions.apiSuccess({ - deploymentState: MlModelDeploymentState.Downloading, - modelId: 'mock-model-id', - targetAllocationCount: 1, - nodeAllocationCount: 1, - threadsPerAllocation: 1, - }); - expect(E5MultilingualCalloutLogic.values.isModelDownloadInProgress).toBe(true); - }); - it('is set to false if the model is downloading', () => { - FetchE5MultilingualModelApiLogic.actions.apiSuccess({ - deploymentState: MlModelDeploymentState.Started, - modelId: 'mock-model-id', - targetAllocationCount: 1, - nodeAllocationCount: 1, - threadsPerAllocation: 1, - }); - expect(E5MultilingualCalloutLogic.values.isModelDownloadInProgress).toBe(false); - }); - }); - - describe('isModelDownloaded', () => { - it('is set to true if the model is downloaded', () => { - FetchE5MultilingualModelApiLogic.actions.apiSuccess({ - deploymentState: MlModelDeploymentState.Downloaded, - modelId: 'mock-model-id', - targetAllocationCount: 1, - nodeAllocationCount: 1, - threadsPerAllocation: 1, - }); - expect(E5MultilingualCalloutLogic.values.isModelDownloaded).toBe(true); - }); - it('is set to false if the model is not downloaded', () => { - FetchE5MultilingualModelApiLogic.actions.apiSuccess({ - deploymentState: MlModelDeploymentState.NotDeployed, - modelId: 'mock-model-id', - targetAllocationCount: 1, - nodeAllocationCount: 1, - threadsPerAllocation: 1, - }); - expect(E5MultilingualCalloutLogic.values.isModelDownloaded).toBe(false); - }); - }); - - describe('isModelRunningSingleThreaded', () => { - it('is set to true if the model has 1 target allocation and 1 thread', () => { - FetchE5MultilingualModelApiLogic.actions.apiSuccess({ - deploymentState: MlModelDeploymentState.FullyAllocated, - modelId: 'mock-model-id', - targetAllocationCount: 1, - nodeAllocationCount: 1, - threadsPerAllocation: 1, - }); - expect(E5MultilingualCalloutLogic.values.isModelRunningSingleThreaded).toBe(true); - }); - it('is set to false if the model has multiple target allocations', () => { - FetchE5MultilingualModelApiLogic.actions.apiSuccess({ - deploymentState: MlModelDeploymentState.FullyAllocated, - modelId: 'mock-model-id', - targetAllocationCount: 2, - nodeAllocationCount: 2, - threadsPerAllocation: 1, - }); - expect(E5MultilingualCalloutLogic.values.isModelRunningSingleThreaded).toBe(false); - }); - it('is set to false if the model runs on multiple threads', () => { - FetchE5MultilingualModelApiLogic.actions.apiSuccess({ - deploymentState: MlModelDeploymentState.FullyAllocated, - modelId: 'mock-model-id', - targetAllocationCount: 1, - nodeAllocationCount: 1, - threadsPerAllocation: 4, - }); - expect(E5MultilingualCalloutLogic.values.isModelRunningSingleThreaded).toBe(false); - }); - }); - - describe('isModelStarted', () => { - it('is set to true if the model is started', () => { - FetchE5MultilingualModelApiLogic.actions.apiSuccess({ - deploymentState: MlModelDeploymentState.FullyAllocated, - modelId: 'mock-model-id', - targetAllocationCount: 1, - nodeAllocationCount: 1, - threadsPerAllocation: 1, - }); - expect(E5MultilingualCalloutLogic.values.isModelStarted).toBe(true); - }); - it('is set to false if the model is not started', () => { - FetchE5MultilingualModelApiLogic.actions.apiSuccess({ - deploymentState: MlModelDeploymentState.NotDeployed, - modelId: 'mock-model-id', - targetAllocationCount: 1, - nodeAllocationCount: 1, - threadsPerAllocation: 1, - }); - expect(E5MultilingualCalloutLogic.values.isModelStarted).toBe(false); - }); - }); - - describe('isPollingE5MultilingualModelActive', () => { - it('is set to false if polling is not active', () => { - mount({ - ...DEFAULT_VALUES, - e5MultilingualModelPollTimeoutId: null, - }); - - expect(E5MultilingualCalloutLogic.values.isPollingE5MultilingualModelActive).toBe(false); - }); - it('is set to true if polling is active', () => { - mount({ - ...DEFAULT_VALUES, - e5MultilingualModelPollTimeoutId: 'timeout-id', - }); - - expect(E5MultilingualCalloutLogic.values.isPollingE5MultilingualModelActive).toBe(true); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout_logic.ts deleted file mode 100644 index 4c2a9acb453bf..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_callout_logic.ts +++ /dev/null @@ -1,308 +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 { kea, MakeLogicType } from 'kea'; - -import { i18n } from '@kbn/i18n'; - -import { HttpError, Status } from '../../../../../../../../common/types/api'; -import { MlModelDeploymentState } from '../../../../../../../../common/types/ml'; -import { getErrorsFromHttpResponse } from '../../../../../../shared/flash_messages/handle_api_errors'; - -import { - CreateE5MultilingualModelApiLogic, - CreateE5MultilingualModelApiLogicActions, - CreateE5MultilingualModelResponse, -} from '../../../../../api/ml_models/e5_multilingual/create_e5_multilingual_model_api_logic'; -import { - FetchE5MultilingualModelApiLogic, - FetchE5MultilingualModelApiLogicActions, - FetchE5MultilingualModelResponse, -} from '../../../../../api/ml_models/e5_multilingual/fetch_e5_multilingual_model_api_logic'; -import { - StartE5MultilingualModelApiLogic, - StartE5MultilingualModelApiLogicActions, -} from '../../../../../api/ml_models/e5_multilingual/start_e5_multilingual_model_api_logic'; - -const FETCH_E5_MULTILINGUAL_MODEL_POLLING_DURATION = 5000; // 5 seconds -const FETCH_E5_MULTILINGUAL_MODEL_POLLING_DURATION_ON_FAILURE = 30000; // 30 seconds -const E5_MULTILINGUAL_MODEL_ID = '.multilingual-e5-small'; - -interface E5MultilingualCalloutActions { - clearE5MultilingualModelPollingId: () => void; - createE5MultilingualModel: () => void; - createE5MultilingualModelMakeRequest: CreateE5MultilingualModelApiLogicActions['makeRequest']; - createE5MultilingualModelPollingTimeout: (duration: number) => { duration: number }; - createE5MultilingualModelSuccess: CreateE5MultilingualModelApiLogicActions['apiSuccess']; - fetchE5MultilingualModel: () => void; - fetchE5MultilingualModelMakeRequest: FetchE5MultilingualModelApiLogicActions['makeRequest']; - fetchE5MultilingualModelError: FetchE5MultilingualModelApiLogicActions['apiError']; - fetchE5MultilingualModelSuccess: FetchE5MultilingualModelApiLogicActions['apiSuccess']; - setE5MultilingualModelPollingId: (pollTimeoutId: ReturnType) => { - pollTimeoutId: ReturnType; - }; - startPollingE5MultilingualModel: () => void; - startE5MultilingualModel: () => void; - startE5MultilingualModelMakeRequest: StartE5MultilingualModelApiLogicActions['makeRequest']; - startE5MultilingualModelSuccess: StartE5MultilingualModelApiLogicActions['apiSuccess']; - stopPollingE5MultilingualModel: () => void; - e5MultilingualModel: FetchE5MultilingualModelApiLogicActions['apiSuccess']; -} - -export interface E5MultilingualCalloutError { - title: string; - message: string; -} - -export interface E5MultilingualCalloutValues { - createE5MultilingualModelError: HttpError | undefined; - createE5MultilingualModelStatus: Status; - createdE5MultilingualModel: CreateE5MultilingualModelResponse | undefined; - fetchE5MultilingualModelError: HttpError | undefined; - isCreateButtonDisabled: boolean; - isModelDownloadInProgress: boolean; - isModelDownloaded: boolean; - isModelRunningSingleThreaded: boolean; - isModelStarted: boolean; - isPollingE5MultilingualModelActive: boolean; - isStartButtonDisabled: boolean; - startE5MultilingualModelError: HttpError | undefined; - startE5MultilingualModelStatus: Status; - e5MultilingualModel: FetchE5MultilingualModelResponse | undefined; - e5MultilingualModelPollTimeoutId: null | ReturnType; - e5MultilingualError: E5MultilingualCalloutError | null; -} - -/** - * Extracts the topmost error in precedence order (create > start > fetch). - * @param createError - * @param fetchError - * @param startError - * @returns the extracted error or null if there is no error - */ -export const getE5MultilingualError = ( - createError: HttpError | undefined, - fetchError: HttpError | undefined, - startError: HttpError | undefined -) => { - return createError !== undefined - ? { - title: i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.e5MultilingualCreateError.title', - { - defaultMessage: 'Error with E5 Multilingual deployment', - } - ), - message: getErrorsFromHttpResponse(createError)[0], - } - : startError !== undefined - ? { - title: i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.e5MultilingualStartError.title', - { - defaultMessage: 'Error starting E5 Multilingual deployment', - } - ), - message: getErrorsFromHttpResponse(startError)[0], - } - : fetchError !== undefined - ? { - title: i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.e5MultilingualFetchError.title', - { - defaultMessage: 'Error fetching E5 Multilingual model', - } - ), - message: getErrorsFromHttpResponse(fetchError)[0], - } - : null; -}; - -export const E5MultilingualCalloutLogic = kea< - MakeLogicType ->({ - actions: { - clearE5MultilingualModelPollingId: true, - createE5MultilingualModelPollingTimeout: (duration) => ({ duration }), - setE5MultilingualModelPollingId: (pollTimeoutId: ReturnType) => ({ - pollTimeoutId, - }), - startPollingE5MultilingualModel: true, - stopPollingE5MultilingualModel: true, - createE5MultilingualModel: true, - fetchE5MultilingualModel: true, - startE5MultilingualModel: true, - }, - connect: { - actions: [ - CreateE5MultilingualModelApiLogic, - [ - 'makeRequest as createE5MultilingualModelMakeRequest', - 'apiSuccess as createE5MultilingualModelSuccess', - 'apiError as createE5MultilingualModelError', - ], - FetchE5MultilingualModelApiLogic, - [ - 'makeRequest as fetchE5MultilingualModelMakeRequest', - 'apiSuccess as fetchE5MultilingualModelSuccess', - 'apiError as fetchE5MultilingualModelError', - ], - StartE5MultilingualModelApiLogic, - [ - 'makeRequest as startE5MultilingualModelMakeRequest', - 'apiSuccess as startE5MultilingualModelSuccess', - 'apiError as startE5MultilingualModelError', - ], - ], - values: [ - CreateE5MultilingualModelApiLogic, - [ - 'data as createdE5MultilingualModel', - 'status as createE5MultilingualModelStatus', - 'error as createE5MultilingualModelError', - ], - FetchE5MultilingualModelApiLogic, - ['data as e5MultilingualModel', 'error as fetchE5MultilingualModelError'], - StartE5MultilingualModelApiLogic, - ['status as startE5MultilingualModelStatus', 'error as startE5MultilingualModelError'], - ], - }, - events: ({ actions, values }) => ({ - afterMount: () => { - actions.fetchE5MultilingualModel(); - }, - beforeUnmount: () => { - if (values.e5MultilingualModelPollTimeoutId !== null) { - actions.stopPollingE5MultilingualModel(); - } - }, - }), - listeners: ({ actions, values }) => ({ - createE5MultilingualModel: () => - actions.createE5MultilingualModelMakeRequest({ modelId: E5_MULTILINGUAL_MODEL_ID }), - fetchE5MultilingualModel: () => - actions.fetchE5MultilingualModelMakeRequest({ modelId: E5_MULTILINGUAL_MODEL_ID }), - startE5MultilingualModel: () => - actions.startE5MultilingualModelMakeRequest({ modelId: E5_MULTILINGUAL_MODEL_ID }), - createE5MultilingualModelPollingTimeout: ({ duration }) => { - if (values.e5MultilingualModelPollTimeoutId !== null) { - clearTimeout(values.e5MultilingualModelPollTimeoutId); - } - const timeoutId = setTimeout(() => { - actions.fetchE5MultilingualModel(); - }, duration); - actions.setE5MultilingualModelPollingId(timeoutId); - }, - createE5MultilingualModelSuccess: () => { - actions.fetchE5MultilingualModel(); - actions.startPollingE5MultilingualModel(); - }, - fetchE5MultilingualModelError: () => { - if (values.isPollingE5MultilingualModelActive) { - actions.createE5MultilingualModelPollingTimeout( - FETCH_E5_MULTILINGUAL_MODEL_POLLING_DURATION_ON_FAILURE - ); - } - }, - fetchE5MultilingualModelSuccess: (data) => { - if (data?.deploymentState === MlModelDeploymentState.Downloading) { - if (!values.isPollingE5MultilingualModelActive) { - actions.startPollingE5MultilingualModel(); - } else { - actions.createE5MultilingualModelPollingTimeout( - FETCH_E5_MULTILINGUAL_MODEL_POLLING_DURATION - ); - } - } else if ( - data?.deploymentState === MlModelDeploymentState.Downloaded && - values.isPollingE5MultilingualModelActive - ) { - actions.stopPollingE5MultilingualModel(); - } - }, - startPollingE5MultilingualModel: () => { - if (values.e5MultilingualModelPollTimeoutId !== null) { - clearTimeout(values.e5MultilingualModelPollTimeoutId); - } - actions.createE5MultilingualModelPollingTimeout(FETCH_E5_MULTILINGUAL_MODEL_POLLING_DURATION); - }, - startE5MultilingualModelSuccess: () => { - actions.fetchE5MultilingualModel(); - }, - stopPollingE5MultilingualModel: () => { - if (values.e5MultilingualModelPollTimeoutId !== null) { - clearTimeout(values.e5MultilingualModelPollTimeoutId); - actions.clearE5MultilingualModelPollingId(); - } - }, - }), - path: ['enterprise_search', 'content', 'e5_multilingual_callout_logic'], - reducers: { - e5MultilingualModelPollTimeoutId: [ - null, - { - clearE5MultilingualModelPollingId: () => null, - setE5MultilingualModelPollingId: (_, { pollTimeoutId }) => pollTimeoutId, - }, - ], - }, - selectors: ({ selectors }) => ({ - isCreateButtonDisabled: [ - () => [selectors.createE5MultilingualModelStatus], - (status: Status) => status !== Status.IDLE && status !== Status.ERROR, - ], - isModelDownloadInProgress: [ - () => [selectors.e5MultilingualModel], - (data: FetchE5MultilingualModelResponse) => - data?.deploymentState === MlModelDeploymentState.Downloading, - ], - isModelDownloaded: [ - () => [selectors.e5MultilingualModel], - (data: FetchE5MultilingualModelResponse) => - data?.deploymentState === MlModelDeploymentState.Downloaded, - ], - isModelStarted: [ - () => [selectors.e5MultilingualModel], - (data: FetchE5MultilingualModelResponse) => - data?.deploymentState === MlModelDeploymentState.Starting || - data?.deploymentState === MlModelDeploymentState.Started || - data?.deploymentState === MlModelDeploymentState.FullyAllocated, - ], - isPollingE5MultilingualModelActive: [ - () => [selectors.e5MultilingualModelPollTimeoutId], - (pollingTimeoutId: E5MultilingualCalloutValues['e5MultilingualModelPollTimeoutId']) => - pollingTimeoutId !== null, - ], - e5MultilingualError: [ - () => [ - selectors.createE5MultilingualModelError, - selectors.fetchE5MultilingualModelError, - selectors.startE5MultilingualModelError, - ], - ( - createE5MultilingualError: E5MultilingualCalloutValues['createE5MultilingualModelError'], - fetchE5MultilingualError: E5MultilingualCalloutValues['fetchE5MultilingualModelError'], - startE5MultilingualError: E5MultilingualCalloutValues['startE5MultilingualModelError'] - ) => - getE5MultilingualError( - createE5MultilingualError, - fetchE5MultilingualError, - startE5MultilingualError - ), - ], - isStartButtonDisabled: [ - () => [selectors.startE5MultilingualModelStatus], - (status: Status) => status !== Status.IDLE && status !== Status.ERROR, - ], - isModelRunningSingleThreaded: [ - () => [selectors.e5MultilingualModel], - (data: FetchE5MultilingualModelResponse) => - // Running single threaded if model has max 1 deployment on 1 node with 1 thread - data?.targetAllocationCount * data?.threadsPerAllocation <= 1, - ], - }), -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_errors.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_errors.test.tsx deleted file mode 100644 index d218c8d111a2b..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_errors.test.tsx +++ /dev/null @@ -1,34 +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 { setMockValues } from '../../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiCallOut } from '@elastic/eui'; - -import { E5MultilingualErrors } from './e5_multilingual_errors'; - -describe('E5MultilingualErrors', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues({}); - }); - const error = { - title: 'some-error-title', - message: 'some-error-message', - }; - it('extracts error panel with the given title and message', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiCallOut).length).toBe(1); - expect(wrapper.find(EuiCallOut).prop('title')).toEqual(error.title); - expect(wrapper.find(EuiCallOut).find('p').length).toBe(1); - expect(wrapper.find(EuiCallOut).find('p').text()).toEqual(error.message); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_errors.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_errors.tsx deleted file mode 100644 index 78038fee5d122..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/e5_multilingual_errors.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { EuiCallOut } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { EuiLinkTo } from '../../../../../../shared/react_router_helpers'; - -import { SendEnterpriseSearchTelemetry } from '../../../../../../shared/telemetry'; - -import { ML_NOTIFICATIONS_PATH } from '../../../../../routes'; - -export function E5MultilingualErrors({ error }: { error: { title: string; message: string } }) { - return ( - <> - - -

{error.message}

- - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.e5MultilingualCreateError.mlNotificationsLink', - { - defaultMessage: 'Machine Learning notifications', - } - )} - -
- - ); -} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployed.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployed.test.tsx deleted file mode 100644 index 58df830f43e2d..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployed.test.tsx +++ /dev/null @@ -1,81 +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 { setMockValues } from '../../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiButton } from '@elastic/eui'; - -import { E5MultilingualDismissButton } from './e5_multilingual_callout'; -import { ModelDeployed } from './model_deployed'; - -const DEFAULT_VALUES = { - startE5MultilingualModelError: undefined, - isCreateButtonDisabled: false, - isModelDownloadInProgress: false, - isModelDownloaded: false, - isModelStarted: false, - isStartButtonDisabled: false, -}; - -describe('ModelDeployed', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(DEFAULT_VALUES); - }); - it('renders start button', () => { - const wrapper = shallow( - {}} - ingestionMethod="crawler" - isDismissable={false} - isStartButtonDisabled={false} - /> - ); - expect(wrapper.find(EuiButton).length).toBe(1); - const button = wrapper.find(EuiButton); - expect(button.prop('disabled')).toBe(false); - }); - it('renders disabled start button if it is set to disabled', () => { - const wrapper = shallow( - {}} - ingestionMethod="crawler" - isDismissable={false} - isStartButtonDisabled - /> - ); - expect(wrapper.find(EuiButton).length).toBe(1); - const button = wrapper.find(EuiButton); - expect(button.prop('disabled')).toBe(true); - }); - it('renders dismiss button if it is set to dismissable', () => { - const wrapper = shallow( - {}} - ingestionMethod="crawler" - isDismissable - isStartButtonDisabled={false} - /> - ); - expect(wrapper.find(E5MultilingualDismissButton).length).toBe(1); - }); - it('does not render dismiss button if it is set to non-dismissable', () => { - const wrapper = shallow( - {}} - ingestionMethod="crawler" - isDismissable={false} - isStartButtonDisabled={false} - /> - ); - expect(wrapper.find(E5MultilingualDismissButton).length).toBe(0); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployed.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployed.tsx deleted file mode 100644 index 20444dd5f7054..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployed.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { useActions } from 'kea'; - -import { - EuiButton, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { - E5MultilingualCallOutState, - E5MultilingualDismissButton, - FineTuneModelsButton, -} from './e5_multilingual_callout'; -import { E5MultilingualCalloutLogic } from './e5_multilingual_callout_logic'; - -export const ModelDeployed = ({ - dismiss, - ingestionMethod, - isDismissable, - isStartButtonDisabled, -}: Pick< - E5MultilingualCallOutState, - 'dismiss' | 'ingestionMethod' | 'isDismissable' | 'isStartButtonDisabled' ->) => { - const { startE5MultilingualModel } = useActions(E5MultilingualCalloutLogic); - - return ( - - - - - - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.deployedTitle', - { defaultMessage: 'Your E5 model has deployed but not started.' } - )} -

-
-
- {isDismissable && ( - - - - )} -
-
- - -

- {i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.deployedBody', - { - defaultMessage: - 'You may start the model in a single-threaded configuration for testing, or tune the performance for a production environment.', - } - )} -

-
-
- - - - - - - startE5MultilingualModel()} - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.e5MultilingualCallOut.startModelButton.label', - { - defaultMessage: 'Start single-threaded', - } - )} - - - - - - - -
-
- ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployment_in_progress.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployment_in_progress.test.tsx deleted file mode 100644 index 1594824aa1a85..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployment_in_progress.test.tsx +++ /dev/null @@ -1,39 +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 { setMockValues } from '../../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { E5MultilingualDismissButton } from './e5_multilingual_callout'; -import { ModelDeploymentInProgress } from './model_deployment_in_progress'; - -const DEFAULT_VALUES = { - startE5MultilingualModelError: undefined, - isCreateButtonDisabled: false, - isModelDownloadInProgress: false, - isModelDownloaded: false, - isModelStarted: false, - isStartButtonDisabled: false, -}; - -describe('ModelDeploymentInProgress', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(DEFAULT_VALUES); - }); - it('renders dismiss button if it is set to dismissable', () => { - const wrapper = shallow( {}} isDismissable />); - expect(wrapper.find(E5MultilingualDismissButton).length).toBe(1); - }); - it('does not render dismiss button if it is set to non-dismissable', () => { - const wrapper = shallow( {}} isDismissable={false} />); - expect(wrapper.find(E5MultilingualDismissButton).length).toBe(0); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployment_in_progress.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployment_in_progress.tsx deleted file mode 100644 index 7f7627a6f03fc..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_deployment_in_progress.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { E5MultilingualCallOutState, E5MultilingualDismissButton } from './e5_multilingual_callout'; - -export const ModelDeploymentInProgress = ({ - dismiss, - isDismissable, -}: Pick) => ( - - - - - - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.deployingTitle', - { defaultMessage: 'Your E5 model is deploying.' } - )} -

-
-
- {isDismissable && ( - - - - )} -
-
- - -

- {i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.deployingBody', - { - defaultMessage: - 'You can continue creating your pipeline with other uploaded models in the meantime.', - } - )} -

-
-
-
-
-); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_started.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_started.test.tsx deleted file mode 100644 index 80563cef58cc4..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_started.test.tsx +++ /dev/null @@ -1,57 +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 { setMockValues } from '../../../../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiText } from '@elastic/eui'; - -import { E5MultilingualDismissButton, FineTuneModelsButton } from './e5_multilingual_callout'; -import { ModelStarted } from './model_started'; - -const DEFAULT_VALUES = { - startE5MultilingualModelError: undefined, - isCreateButtonDisabled: false, - isModelDownloadInProgress: false, - isModelDownloaded: false, - isModelStarted: false, - isStartButtonDisabled: false, -}; - -describe('ModelStarted', () => { - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(DEFAULT_VALUES); - }); - it('renders dismiss button if it is set to dismissable', () => { - const wrapper = shallow( - {}} isCompact={false} isDismissable isSingleThreaded /> - ); - expect(wrapper.find(E5MultilingualDismissButton).length).toBe(1); - }); - it('does not render dismiss button if it is set to non-dismissable', () => { - const wrapper = shallow( - {}} isCompact={false} isDismissable={false} isSingleThreaded /> - ); - expect(wrapper.find(E5MultilingualDismissButton).length).toBe(0); - }); - it('renders fine-tune button if the model is running single-threaded', () => { - const wrapper = shallow( - {}} isCompact={false} isDismissable isSingleThreaded /> - ); - expect(wrapper.find(FineTuneModelsButton).length).toBe(1); - }); - it('does not render description if it is set to compact', () => { - const wrapper = shallow( - {}} isCompact isDismissable isSingleThreaded /> - ); - expect(wrapper.find(EuiText).length).toBe(1); // Title only - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_started.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_started.tsx deleted file mode 100644 index c6a444373886c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/e5_multilingual_callout/model_started.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { - EuiButtonEmpty, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { KibanaLogic } from '../../../../../../shared/kibana'; - -import { TRAINED_MODELS_PATH } from '../utils'; - -import { - E5MultilingualCallOutState, - E5MultilingualDismissButton, - FineTuneModelsButton, -} from './e5_multilingual_callout'; - -export const ModelStarted = ({ - dismiss, - isCompact, - isDismissable, - isSingleThreaded, -}: Pick< - E5MultilingualCallOutState, - 'dismiss' | 'isCompact' | 'isDismissable' | 'isSingleThreaded' ->) => ( - - - - - - - - - -

- {isSingleThreaded - ? isCompact - ? i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.startedSingleThreadedTitleCompact', - { defaultMessage: 'Your E5 model is running single-threaded.' } - ) - : i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.startedSingleThreadedTitle', - { defaultMessage: 'Your E5 model has started single-threaded.' } - ) - : isCompact - ? i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.startedTitleCompact', - { defaultMessage: 'Your E5 model is running.' } - ) - : i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.startedTitle', - { defaultMessage: 'Your E5 model has started.' } - )} -

-
-
- {isDismissable && ( - - - - )} -
-
- {!isCompact && ( - <> - - -

- {isSingleThreaded - ? i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.startedSingleThreadedBody', - { - defaultMessage: - 'This single-threaded configuration is great for testing your custom inference pipelines, however performance should be fine-tuned for production.', - } - ) - : i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.e5MultilingualCallOut.startedBody', - { - defaultMessage: 'Enjoy the power of E5 in your custom Inference pipeline.', - } - )} -

-
-
- - - - {isSingleThreaded ? ( - - ) : ( - - KibanaLogic.values.navigateToUrl(TRAINED_MODELS_PATH, { - shouldNotCreateHref: true, - }) - } - > - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.e5MultilingualCallOut.viewModelsButton', - { - defaultMessage: 'View details', - } - )} - - )} - - - - - )} -
-
-); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout/deploy_model.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout/deploy_model.tsx index 74ce840252a8b..4320fff515b4e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout/deploy_model.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout/deploy_model.tsx @@ -19,7 +19,7 @@ import { EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FormattedHTMLMessage, FormattedMessage } from '@kbn/i18n-react'; +import { FormattedMessage, FormattedHTMLMessage } from '@kbn/i18n-react'; import { docLinks } from '../../../../../../shared/doc_links'; From 7c0f5ef8cfc73204de85832f107d67b3d803c628 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 10 Jan 2024 16:18:38 +0000 Subject: [PATCH 04/12] skip flaky suite (#174384) --- .../registered_attachments_property_actions.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx index 273ac1db45eaf..8cbc69e30039f 100644 --- a/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions/registered_attachments_property_actions.test.tsx @@ -18,7 +18,8 @@ import { import { RegisteredAttachmentsPropertyActions } from './registered_attachments_property_actions'; import { AttachmentActionType } from '../../../client/attachment_framework/types'; -describe('RegisteredAttachmentsPropertyActions', () => { +// FLAKY: https://github.com/elastic/kibana/issues/174384 +describe.skip('RegisteredAttachmentsPropertyActions', () => { let appMock: AppMockRenderer; const props = { From 88b74ba39f1c2cac267b7cd2d4ff4951e81f22f3 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Wed, 10 Jan 2024 11:22:50 -0500 Subject: [PATCH 05/12] [Fleet] Don't throw concurrent installation error when encountering conflicts (#173190) ## Summary Closes https://github.com/elastic/kibana/issues/171986 ## How to test You'll need to get into a state where a tag with a conflicting ID exists in a second Kibana space outside of the default space. It seems like the tags API doesn't support providing an ID at least from the REST API, so that makes creating duplicate tags in other spaces a bit challenging. e.g. ``` POST kbn:api/saved_objects_tagging/tags/create { "id": "my-tag", "name": "My tag", "description": "", "color": "#FFFFFF" } ``` results in ``` { "statusCode": 400, "error": "Bad Request", "message": "[request body.id]: definition for this key is missing" } ``` So, in order to enable creating tags with ID's easily, I made some quick code changes to the tags service ```diff diff --git a/x-pack/plugins/saved_objects_tagging/server/routes/tags/create_tag.ts b/x-pack/plugins/saved_objects_tagging/server/routes/tags/create_tag.ts index 0c48168eed2..fa0e7fbe7b9 100644 --- a/x-pack/plugins/saved_objects_tagging/server/routes/tags/create_tag.ts +++ b/x-pack/plugins/saved_objects_tagging/server/routes/tags/create_tag.ts @@ -6,6 +6,7 @@ */ import { schema } from '@kbn/config-schema'; +import { omit } from 'lodash'; import type { TagsPluginRouter } from '../../types'; import { TagValidationError } from '../../services/tags'; @@ -15,6 +16,7 @@ export const registerCreateTagRoute = (router: TagsPluginRouter) => { path: '/api/saved_objects_tagging/tags/create', validate: { body: schema.object({ + id: schema.maybe(schema.string()), name: schema.string(), description: schema.string(), color: schema.string(), @@ -32,7 +34,7 @@ export const registerCreateTagRoute = (router: TagsPluginRouter) => { }); } - const tag = await tagsClient.create(req.body); + const tag = await tagsClient.create(omit(req.body, 'id'), { id: req.body.id }); return res.ok({ body: { tag, ``` With those changes in place (I don't think committing them is necessary), I was able to create a tag in my second space e.g. ``` POST kbn:api/saved_objects_tagging/tags/create { "id": "fleet-pkg-nginx-default", "name": "Nginx duplicate tag", "description": "", "color": "#DADADA" } ``` Then, from my default space I installed the nginx package ``` POST kbn:/api/fleet/epm/packages/nginx ``` This throws the concurrent install error as expected on `main` ``` { "statusCode": 409, "error": "Conflict", "message": "Concurrent installation or upgrade of nginx-1.17.0 detected, aborting. Original error: Saved object [tag/fleet-pkg-nginx-default] conflict" } ``` With this PR, we get this error instead ``` { "statusCode": 400, "error": "Bad Request", "message": "Saved Object conflict encountered while installing nginx-1.17.0. There may be a conflicting Saved Object saved to another Space. Original error: Saved object [tag/fleet-pkg-nginx-default] conflict" } ``` --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/fleet/server/errors/handlers.ts | 4 ++ x-pack/plugins/fleet/server/errors/index.ts | 1 + .../epm/packages/_install_package.test.ts | 55 ++++++++++++++++++- .../services/epm/packages/_install_package.ts | 10 ++-- 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index c85bfeced9db1..fef8cd1872413 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -42,6 +42,7 @@ import { AgentRequestInvalidError, PackagePolicyRequestError, FleetNotFoundError, + PackageSavedObjectConflictError, } from '.'; type IngestErrorHandler = ( @@ -100,6 +101,9 @@ const getHTTPResponseCode = (error: FleetError): number => { if (error instanceof ConcurrentInstallOperationError) { return 409; } + if (error instanceof PackageSavedObjectConflictError) { + return 409; + } if (error instanceof PackagePolicyNameExistsError) { return 409; } diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index ce7245672e623..6a69581c11965 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -43,6 +43,7 @@ export class PackageInvalidArchiveError extends FleetError {} export class PackageRemovalError extends FleetError {} export class PackageESError extends FleetError {} export class ConcurrentInstallOperationError extends FleetError {} +export class PackageSavedObjectConflictError extends FleetError {} export class KibanaSOReferenceError extends FleetError {} export class PackageAlreadyInstalledError extends FleetError {} diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts index d6d653fd98c4e..ce416a6277313 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.test.ts @@ -14,7 +14,7 @@ import { savedObjectsClientMock, elasticsearchServiceMock } from '@kbn/core/serv import { loggerMock } from '@kbn/logging-mocks'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; -import { ConcurrentInstallOperationError } from '../../../errors'; +import { ConcurrentInstallOperationError, PackageSavedObjectConflictError } from '../../../errors'; import type { Installation } from '../../../../common'; @@ -254,7 +254,6 @@ describe('_installPackage', () => { }); describe('when package is stuck in `installing`', () => { - afterEach(() => {}); const mockInstalledPackageSo: SavedObject = { id: 'mocked-package', attributes: { @@ -387,4 +386,56 @@ describe('_installPackage', () => { }); }); }); + + it('surfaces saved object conflicts error', () => { + appContextService.start( + createAppContextStartContractMock({ + internal: { + disableILMPolicies: false, + disableProxies: false, + fleetServerStandalone: false, + onlyAllowAgentUpgradeToKnownVersions: false, + retrySetupOnBoot: false, + registry: { + kibanaVersionCheckEnabled: true, + capabilities: [], + excludePackages: [], + }, + }, + }) + ); + + mockedInstallKibanaAssetsAndReferences.mockRejectedValueOnce( + new PackageSavedObjectConflictError('test') + ); + + expect( + _installPackage({ + savedObjectsClient: soClient, + // @ts-ignore + savedObjectsImporter: jest.fn(), + esClient, + logger: loggerMock.create(), + packageInstallContext: { + packageInfo: { + title: 'title', + name: 'xyz', + version: '4.5.6', + description: 'test', + type: 'integration', + categories: ['cloud', 'custom'], + format_version: 'string', + release: 'experimental', + conditions: { kibana: { version: 'x.y.z' } }, + owner: { github: 'elastic/fleet' }, + } as any, + assetsMap: new Map(), + paths: [], + }, + installType: 'install', + installSource: 'registry', + spaceId: DEFAULT_SPACE_ID, + }) + ).rejects.toThrowError(PackageSavedObjectConflictError); + }); }); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index 6c30d3a8d332d..e182fd8721075 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -45,7 +45,7 @@ import { installTransforms } from '../elasticsearch/transform/install'; import { installMlModel } from '../elasticsearch/ml_model'; import { installIlmForDataStream } from '../elasticsearch/datastream_ilm/install'; import { saveArchiveEntriesFromAssetsMap } from '../archive/storage'; -import { ConcurrentInstallOperationError } from '../../../errors'; +import { ConcurrentInstallOperationError, PackageSavedObjectConflictError } from '../../../errors'; import { appContextService, packagePolicyService } from '../..'; import { auditLoggingService } from '../../audit_logging'; @@ -387,10 +387,12 @@ export async function _installPackage({ return [...installedKibanaAssetsRefs, ...esReferences]; } catch (err) { if (SavedObjectsErrorHelpers.isConflictError(err)) { - throw new ConcurrentInstallOperationError( - `Concurrent installation or upgrade of ${pkgName || 'unknown'}-${ + throw new PackageSavedObjectConflictError( + `Saved Object conflict encountered while installing ${pkgName || 'unknown'}-${ pkgVersion || 'unknown' - } detected, aborting. Original error: ${err.message}` + }. There may be a conflicting Saved Object saved to another Space. Original error: ${ + err.message + }` ); } else { throw err; From 5a8b7afe0bf89c094707fabdc5fd26457346632a Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 10 Jan 2024 09:58:34 -0700 Subject: [PATCH 06/12] [Reporting ] run task function simplifications of image export types (#174410) ## Summary This PR cleans up extra layers of abstraction in image export types that could complicate progress of the proposal of "auto" timeouts. See https://github.com/elastic/kibana/issues/131852 ## Changes Minor code cleanup of the "runTask" functions of export types that create screenshots, by removing the `generatePdf*` / `generatePng` helper callback functions and inlining the work those modules were doing. The helper modules were an integral part of the screenshotting pipelines, but in the unit tests they were completely mocked. Now that we have a proper mock utility of the `screenshotting` plugin start contract, we no longer need mockable code in a separate layer of the pipelines. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../export_types/pdf/generate_pdf.ts | 58 ---------- .../export_types/pdf/generate_pdf_v2.ts | 75 ------------- .../export_types/pdf/printable_pdf.test.ts | 42 ++++---- .../export_types/pdf/printable_pdf.ts | 60 +++++++---- .../export_types/pdf/printable_pdf_v2.test.ts | 77 +++++++------ .../export_types/pdf/printable_pdf_v2.ts | 101 ++++++++++-------- .../export_types/png/generate_png.ts | 73 ------------- .../export_types/png/png_v2.test.ts | 69 ++++++------ .../kbn-reporting/export_types/png/png_v2.ts | 71 ++++++++---- .../export_types/png/tsconfig.json | 1 - 10 files changed, 251 insertions(+), 376 deletions(-) delete mode 100644 packages/kbn-reporting/export_types/pdf/generate_pdf.ts delete mode 100644 packages/kbn-reporting/export_types/pdf/generate_pdf_v2.ts delete mode 100644 packages/kbn-reporting/export_types/png/generate_png.ts diff --git a/packages/kbn-reporting/export_types/pdf/generate_pdf.ts b/packages/kbn-reporting/export_types/pdf/generate_pdf.ts deleted file mode 100644 index 5703e16e48abc..0000000000000 --- a/packages/kbn-reporting/export_types/pdf/generate_pdf.ts +++ /dev/null @@ -1,58 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Observable } from 'rxjs'; -import { mergeMap, tap } from 'rxjs/operators'; - -import type { PdfScreenshotOptions, PdfScreenshotResult } from '@kbn/screenshotting-plugin/server'; -import { getTracker } from './pdf_tracker'; - -interface PdfResult { - buffer: Uint8Array | null; - metrics?: PdfScreenshotResult['metrics']; - warnings: string[]; -} - -type GetScreenshotsFn = (options: PdfScreenshotOptions) => Observable; - -export function generatePdfObservable( - getScreenshots: GetScreenshotsFn, - options: PdfScreenshotOptions -): Observable { - const tracker = getTracker(); - tracker.startScreenshots(); - - return getScreenshots(options).pipe( - tap(({ metrics }) => { - if (metrics.cpu) { - tracker.setCpuUsage(metrics.cpu); - } - if (metrics.memory) { - tracker.setMemoryUsage(metrics.memory); - } - }), - mergeMap(async ({ data: buffer, errors, metrics, renderErrors }) => { - tracker.endScreenshots(); - const warnings: string[] = []; - if (errors) { - warnings.push(...errors.map((error) => error.message)); - } - if (renderErrors) { - warnings.push(...renderErrors); - } - - tracker.end(); - - return { - buffer, - metrics, - warnings, - }; - }) - ); -} diff --git a/packages/kbn-reporting/export_types/pdf/generate_pdf_v2.ts b/packages/kbn-reporting/export_types/pdf/generate_pdf_v2.ts deleted file mode 100644 index 54489c6258bd8..0000000000000 --- a/packages/kbn-reporting/export_types/pdf/generate_pdf_v2.ts +++ /dev/null @@ -1,75 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Observable } from 'rxjs'; -import { mergeMap, tap } from 'rxjs/operators'; - -import type { LocatorParams, ReportingServerInfo } from '@kbn/reporting-common/types'; -import type { TaskPayloadPDFV2 } from '@kbn/reporting-export-types-pdf-common'; -import type { PdfScreenshotOptions, PdfScreenshotResult } from '@kbn/screenshotting-plugin/server'; -import type { UrlOrUrlWithContext } from '@kbn/screenshotting-plugin/server/screenshots'; -import { type ReportingConfigType, getFullRedirectAppUrl } from '@kbn/reporting-server'; - -import { getTracker } from './pdf_tracker'; - -interface PdfResult { - buffer: Uint8Array | null; - metrics?: PdfScreenshotResult['metrics']; - warnings: string[]; -} - -type GetScreenshotsFn = (options: PdfScreenshotOptions) => Observable; - -export function generatePdfObservableV2( - config: ReportingConfigType, - serverInfo: ReportingServerInfo, - getScreenshots: GetScreenshotsFn, - job: TaskPayloadPDFV2, - locatorParams: LocatorParams[], - options: Omit -): Observable { - const tracker = getTracker(); - tracker.startScreenshots(); - - /** - * For each locator we get the relative URL to the redirect app - */ - const urls = locatorParams.map((locator) => [ - getFullRedirectAppUrl(config, serverInfo, job.spaceId, job.forceNow), - locator, - ]) as unknown as UrlOrUrlWithContext[]; - - const screenshots$ = getScreenshots({ ...options, urls }).pipe( - tap(({ metrics }) => { - if (metrics.cpu) { - tracker.setCpuUsage(metrics.cpu); - } - if (metrics.memory) { - tracker.setMemoryUsage(metrics.memory); - } - }), - mergeMap(async ({ data: buffer, errors, metrics, renderErrors }) => { - tracker.endScreenshots(); - const warnings: string[] = []; - if (errors) { - warnings.push(...errors.map((error) => error.message)); - } - if (renderErrors) { - warnings.push(...renderErrors); - } - - return { - buffer, - metrics, - warnings, - }; - }) - ); - - return screenshots$; -} diff --git a/packages/kbn-reporting/export_types/pdf/printable_pdf.test.ts b/packages/kbn-reporting/export_types/pdf/printable_pdf.test.ts index 9ca0fa34effcd..10c21ede18227 100644 --- a/packages/kbn-reporting/export_types/pdf/printable_pdf.test.ts +++ b/packages/kbn-reporting/export_types/pdf/printable_pdf.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { of } from 'rxjs'; +import * as Rx from 'rxjs'; import { Writable } from 'stream'; import { coreMock, elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; @@ -14,12 +14,8 @@ import { CancellationToken } from '@kbn/reporting-common'; import { TaskPayloadPDF } from '@kbn/reporting-export-types-pdf-common'; import { createMockConfigSchema } from '@kbn/reporting-mocks-server'; import { cryptoFactory } from '@kbn/reporting-server'; -import { ScreenshottingStart } from '@kbn/screenshotting-plugin/server'; - +import { createMockScreenshottingStart } from '@kbn/screenshotting-plugin/server/mock'; import { PdfV1ExportType } from '.'; -import { generatePdfObservable } from './generate_pdf'; - -jest.mock('./generate_pdf'); let content: string; let mockPdfExportType: PdfV1ExportType; @@ -34,6 +30,9 @@ const encryptHeaders = async (headers: Record) => { return await crypto.encrypt(headers); }; +const screenshottingMock = createMockScreenshottingStart(); +const getScreenshotsSpy = jest.spyOn(screenshottingMock, 'getScreenshots'); +const testContent = 'raw string from get_screenhots'; const getBasePayload = (baseObj: any) => baseObj as TaskPayloadPDF; beforeEach(async () => { @@ -54,15 +53,20 @@ beforeEach(async () => { esClient: elasticsearchServiceMock.createClusterClient(), savedObjects: mockCoreStart.savedObjects, uiSettings: mockCoreStart.uiSettings, - screenshotting: {} as unknown as ScreenshottingStart, + screenshotting: screenshottingMock, + }); + getScreenshotsSpy.mockImplementation(() => { + return Rx.of({ + metrics: { cpu: 0, pages: 1 }, + data: Buffer.from(testContent), + errors: [], + renderErrors: [], + }); }); }); -afterEach(() => (generatePdfObservable as jest.Mock).mockReset()); - test(`passes browserTimezone to generatePdf`, async () => { const encryptedHeaders = await encryptHeaders({}); - (generatePdfObservable as jest.Mock).mockReturnValue(of({ buffer: Buffer.from('') })); const browserTimezone = 'UTC'; await mockPdfExportType.runTask( @@ -76,17 +80,20 @@ test(`passes browserTimezone to generatePdf`, async () => { stream ); - expect(generatePdfObservable).toHaveBeenCalledWith( - expect.anything(), - expect.objectContaining({ browserTimezone: 'UTC' }) - ); + expect(getScreenshotsSpy).toHaveBeenCalledWith({ + browserTimezone: 'UTC', + format: 'pdf', + headers: {}, + layout: undefined, + logo: false, + title: undefined, + urls: ['http://localhost:80/mock-server-basepath/app/kibana#/something'], + }); }); test(`returns content_type of application/pdf`, async () => { const encryptedHeaders = await encryptHeaders({}); - (generatePdfObservable as jest.Mock).mockReturnValue(of({ buffer: Buffer.from('') })); - const { content_type: contentType } = await mockPdfExportType.runTask( 'pdfJobId', getBasePayload({ objects: [], headers: encryptedHeaders }), @@ -97,9 +104,6 @@ test(`returns content_type of application/pdf`, async () => { }); test(`returns content of generatePdf getBuffer base64 encoded`, async () => { - const testContent = 'test content'; - (generatePdfObservable as jest.Mock).mockReturnValue(of({ buffer: Buffer.from(testContent) })); - const encryptedHeaders = await encryptHeaders({}); await mockPdfExportType.runTask( 'pdfJobId', diff --git a/packages/kbn-reporting/export_types/pdf/printable_pdf.ts b/packages/kbn-reporting/export_types/pdf/printable_pdf.ts index 6c8ce2a5b1d0d..6aaf0e5c491e2 100644 --- a/packages/kbn-reporting/export_types/pdf/printable_pdf.ts +++ b/packages/kbn-reporting/export_types/pdf/printable_pdf.ts @@ -29,10 +29,10 @@ import { } from '@kbn/reporting-export-types-pdf-common'; import { ExportType, decryptJobHeaders } from '@kbn/reporting-server'; -import { generatePdfObservable } from './generate_pdf'; -import { validateUrls } from './validate_urls'; import { getCustomLogo } from './get_custom_logo'; import { getFullUrls } from './get_full_urls'; +import { getTracker } from './pdf_tracker'; +import { validateUrls } from './validate_urls'; /** * @deprecated @@ -76,15 +76,15 @@ export class PdfV1ExportType extends ExportType { - const jobLogger = this.logger.get(`execute-job:${jobId}`); + const logger = this.logger.get(`execute-job:${jobId}`); const apmTrans = apm.startTransaction('execute-job-pdf', REPORTING_TRANSACTION_TYPE); const apmGetAssets = apmTrans.startSpan('get-assets', 'setup'); let apmGeneratePdf: { end: () => void } | null | undefined; const process$: Observable = of(1).pipe( - mergeMap(() => decryptJobHeaders(this.config.encryptionKey, job.headers, jobLogger)), + mergeMap(() => decryptJobHeaders(this.config.encryptionKey, job.headers, logger)), mergeMap(async (headers) => { - const fakeRequest = this.getFakeRequest(headers, job.spaceId, jobLogger); + const fakeRequest = this.getFakeRequest(headers, job.spaceId, logger); const uiSettingsClient = await this.getUiSettingsClient(fakeRequest); return getCustomLogo(uiSettingsClient, headers); }), @@ -96,18 +96,11 @@ export class PdfV1ExportType extends ExportType - this.startDeps.screenshotting!.getScreenshots({ - format: 'pdf', - title, - logo, - urls, - browserTimezone, - headers, - layout, - }), - { + const tracker = getTracker(); + tracker.startScreenshots(); + + return this.startDeps + .screenshotting!.getScreenshots({ format: 'pdf', title, logo, @@ -115,8 +108,35 @@ export class PdfV1ExportType extends ExportType { + if (metrics.cpu) { + tracker.setCpuUsage(metrics.cpu); + } + if (metrics.memory) { + tracker.setMemoryUsage(metrics.memory); + } + }), + mergeMap(async ({ data: buffer, errors, metrics, renderErrors }) => { + tracker.endScreenshots(); + const warnings: string[] = []; + if (errors) { + warnings.push(...errors.map((error) => error.message)); + } + if (renderErrors) { + warnings.push(...renderErrors); + } + + tracker.end(); + + return { + buffer, + metrics, + warnings, + }; + }) + ); }), tap(({ buffer }) => { apmGeneratePdf?.end(); @@ -130,7 +150,7 @@ export class PdfV1ExportType extends ExportType { - jobLogger.error(err); + logger.error(err); return throwError(err); }) ); diff --git a/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.test.ts b/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.test.ts index 4e4f0c1491130..28d28ade31e97 100644 --- a/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.test.ts +++ b/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { of } from 'rxjs'; -import type { Writable } from 'stream'; +import * as Rx from 'rxjs'; +import { Writable } from 'stream'; import { coreMock, elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { CancellationToken } from '@kbn/reporting-common'; @@ -15,12 +15,8 @@ import type { LocatorParams } from '@kbn/reporting-common/types'; import type { TaskPayloadPDFV2 } from '@kbn/reporting-export-types-pdf-common'; import { createMockConfigSchema } from '@kbn/reporting-mocks-server'; import { cryptoFactory } from '@kbn/reporting-server'; -import type { ScreenshottingStart } from '@kbn/screenshotting-plugin/server'; - +import { createMockScreenshottingStart } from '@kbn/screenshotting-plugin/server/mock'; import { PdfExportType } from '.'; -import { generatePdfObservableV2 } from './generate_pdf_v2'; - -jest.mock('./generate_pdf_v2'); let content: string; let mockPdfExportType: PdfExportType; @@ -34,7 +30,11 @@ const encryptHeaders = async (headers: Record) => { const crypto = cryptoFactory(mockEncryptionKey); return await crypto.encrypt(headers); }; +let encryptedHeaders: string; +const screenshottingMock = createMockScreenshottingStart(); +const getScreenshotsSpy = jest.spyOn(screenshottingMock, 'getScreenshots'); +const testContent = 'raw string from get_screenhots'; const getBasePayload = (baseObj: any) => ({ params: { forceNow: 'test' }, @@ -50,8 +50,10 @@ beforeEach(async () => { const mockCoreSetup = coreMock.createSetup(); const mockCoreStart = coreMock.createStart(); - mockPdfExportType = new PdfExportType(mockCoreSetup, configType, mockLogger, context); + encryptedHeaders = await encryptHeaders({}); + + mockPdfExportType = new PdfExportType(mockCoreSetup, configType, mockLogger, context); mockPdfExportType.setup({ basePath: { set: jest.fn() }, }); @@ -59,21 +61,26 @@ beforeEach(async () => { esClient: elasticsearchServiceMock.createClusterClient(), savedObjects: mockCoreStart.savedObjects, uiSettings: mockCoreStart.uiSettings, - screenshotting: {} as unknown as ScreenshottingStart, + screenshotting: screenshottingMock, }); -}); -afterEach(() => (generatePdfObservableV2 as jest.Mock).mockReset()); + getScreenshotsSpy.mockImplementation(() => { + return Rx.of({ + metrics: { cpu: 0, pages: 1 }, + data: Buffer.from(testContent), + errors: [], + renderErrors: [], + }); + }); +}); test(`passes browserTimezone to generatePdf`, async () => { - const encryptedHeaders = await encryptHeaders({}); - (generatePdfObservableV2 as jest.Mock).mockReturnValue(of(Buffer.from(''))); - const browserTimezone = 'UTC'; await mockPdfExportType.runTask( 'pdfJobId', getBasePayload({ forceNow: 'test', + layout: { dimensions: {} }, title: 'PDF Params Timezone Test', locatorParams: [{ version: 'test', id: 'test' }] as LocatorParams[], browserTimezone, @@ -83,24 +90,30 @@ test(`passes browserTimezone to generatePdf`, async () => { stream ); - expect(generatePdfObservableV2).toHaveBeenCalledWith( - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - expect.anything(), - expect.objectContaining({ browserTimezone: 'UTC' }) - ); + expect(getScreenshotsSpy).toHaveBeenCalledWith({ + browserTimezone: 'UTC', + format: 'pdf', + headers: {}, + layout: { dimensions: {} }, + logo: false, + title: 'PDF Params Timezone Test', + urls: [ + [ + 'http://localhost:80/mock-server-basepath/app/reportingRedirect?forceNow=test', + { __REPORTING_REDIRECT_LOCATOR_STORE_KEY__: { id: 'test', version: 'test' } }, + ], + ], + }); }); test(`returns content_type of application/pdf`, async () => { - const encryptedHeaders = await encryptHeaders({}); - - (generatePdfObservableV2 as jest.Mock).mockReturnValue(of({ buffer: Buffer.from('') })); - const { content_type: contentType } = await mockPdfExportType.runTask( 'pdfJobId', - getBasePayload({ locatorParams: [], headers: encryptedHeaders }), + getBasePayload({ + layout: { dimensions: {} }, + locatorParams: [{ version: 'test', id: 'test' }] as LocatorParams[], + headers: encryptedHeaders, + }), cancellationToken, stream ); @@ -108,13 +121,13 @@ test(`returns content_type of application/pdf`, async () => { }); test(`returns content of generatePdf getBuffer base64 encoded`, async () => { - const testContent = 'test content'; - (generatePdfObservableV2 as jest.Mock).mockReturnValue(of({ buffer: Buffer.from(testContent) })); - - const encryptedHeaders = await encryptHeaders({}); await mockPdfExportType.runTask( 'pdfJobId', - getBasePayload({ locatorParams: [], headers: encryptedHeaders }), + getBasePayload({ + layout: { dimensions: {} }, + locatorParams: [{ version: 'test', id: 'test' }] as LocatorParams[], + headers: encryptedHeaders, + }), cancellationToken, stream ); diff --git a/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.ts b/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.ts index cc975c536803d..24e445e503d51 100644 --- a/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.ts +++ b/packages/kbn-reporting/export_types/pdf/printable_pdf_v2.ts @@ -22,17 +22,18 @@ import { REPORTING_REDIRECT_LOCATOR_STORE_KEY, REPORTING_TRANSACTION_TYPE, } from '@kbn/reporting-common'; -import type { TaskRunResult, UrlOrUrlLocatorTuple } from '@kbn/reporting-common/types'; +import type { TaskRunResult } from '@kbn/reporting-common/types'; +import type { TaskPayloadPDFV2 } from '@kbn/reporting-export-types-pdf-common'; import { JobParamsPDFV2, PDF_JOB_TYPE_V2, PDF_REPORT_TYPE_V2, - TaskPayloadPDFV2, } from '@kbn/reporting-export-types-pdf-common'; -import { decryptJobHeaders, getFullRedirectAppUrl, ExportType } from '@kbn/reporting-server'; +import { ExportType, decryptJobHeaders, getFullRedirectAppUrl } from '@kbn/reporting-server'; +import type { UrlOrUrlWithContext } from '@kbn/screenshotting-plugin/server/screenshots'; -import { generatePdfObservableV2 } from './generate_pdf_v2'; import { getCustomLogo } from './get_custom_logo'; +import { getTracker } from './pdf_tracker'; export class PdfExportType extends ExportType { id = PDF_REPORT_TYPE_V2; @@ -80,66 +81,82 @@ export class PdfExportType extends ExportType cancellationToken: CancellationToken, stream: Writable ) => { - const jobLogger = this.logger.get(`execute-job:${jobId}`); + const logger = this.logger.get(`execute-job:${jobId}`); const apmTrans = apm.startTransaction('execute-job-pdf-v2', REPORTING_TRANSACTION_TYPE); const apmGetAssets = apmTrans.startSpan('get-assets', 'setup'); let apmGeneratePdf: { end: () => void } | null | undefined; const { encryptionKey } = this.config; const process$: Rx.Observable = of(1).pipe( - mergeMap(() => decryptJobHeaders(encryptionKey, payload.headers, jobLogger)), + mergeMap(() => decryptJobHeaders(encryptionKey, payload.headers, logger)), mergeMap(async (headers: Headers) => { - const fakeRequest = this.getFakeRequest(headers, payload.spaceId, jobLogger); + const fakeRequest = this.getFakeRequest(headers, payload.spaceId, logger); const uiSettingsClient = await this.getUiSettingsClient(fakeRequest); return await getCustomLogo(uiSettingsClient, headers); }), mergeMap(({ logo, headers }) => { const { browserTimezone, layout, title, locatorParams } = payload; - let urls: UrlOrUrlLocatorTuple[]; - if (locatorParams) { - urls = locatorParams.map((locator) => [ - getFullRedirectAppUrl( - this.config, - this.getServerInfo(), - payload.spaceId, - payload.forceNow - ), - locator, - ]); - } apmGetAssets?.end(); apmGeneratePdf = apmTrans.startSpan('generate-pdf-pipeline', 'execute'); - return generatePdfObservableV2( - this.config, - this.getServerInfo(), - () => - this.startDeps.screenshotting!.getScreenshots({ - format: 'pdf', - title, - logo, - browserTimezone, - headers, - layout, - urls: urls.map((url) => - typeof url === 'string' - ? url - : [url[0], { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: url[1] }] - ), - }), - payload, - locatorParams, - { + const tracker = getTracker(); + tracker.startScreenshots(); + + /** + * For each locator we get the relative URL to the redirect app + */ + const urls = locatorParams.map((locator) => [ + getFullRedirectAppUrl( + this.config, + this.getServerInfo(), + payload.spaceId, + payload.forceNow + ), + locator, + ]) as unknown as UrlOrUrlWithContext[]; + + return this.startDeps + .screenshotting!.getScreenshots({ format: 'pdf', title, logo, browserTimezone, headers, layout, - } - ); + urls: urls.map((url) => + typeof url === 'string' + ? url + : [url[0], { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: url[1] }] + ), + }) + .pipe( + tap(({ metrics }) => { + if (metrics.cpu) { + tracker.setCpuUsage(metrics.cpu); + } + if (metrics.memory) { + tracker.setMemoryUsage(metrics.memory); + } + }), + mergeMap(async ({ data: buffer, errors, metrics, renderErrors }) => { + tracker.endScreenshots(); + const warnings: string[] = []; + if (errors) { + warnings.push(...errors.map((error) => error.message)); + } + if (renderErrors) { + warnings.push(...renderErrors); + } + + return { + buffer, + metrics, + warnings, + }; + }) + ); }), tap(({ buffer }) => { apmGeneratePdf?.end(); @@ -154,7 +171,7 @@ export class PdfExportType extends ExportType warnings, })), catchError((err) => { - jobLogger.error(err); + logger.error(err); return Rx.throwError(() => err); }) ); diff --git a/packages/kbn-reporting/export_types/png/generate_png.ts b/packages/kbn-reporting/export_types/png/generate_png.ts deleted file mode 100644 index d0ce9b9791690..0000000000000 --- a/packages/kbn-reporting/export_types/png/generate_png.ts +++ /dev/null @@ -1,73 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import apm from 'elastic-apm-node'; -import { Observable } from 'rxjs'; -import { finalize, map, tap } from 'rxjs/operators'; - -import type { Logger } from '@kbn/logging'; -import { REPORTING_TRANSACTION_TYPE } from '@kbn/reporting-common/constants'; -import type { PngScreenshotOptions, PngScreenshotResult } from '@kbn/screenshotting-plugin/server'; - -interface PngResult { - buffer: Buffer; - metrics?: PngScreenshotResult['metrics']; - warnings: string[]; -} - -type GetScreenshotsFn = (options: PngScreenshotOptions) => Observable; - -export function generatePngObservable( - getScreenshots: GetScreenshotsFn, - logger: Logger, - options: Omit -): Observable { - const apmTrans = apm.startTransaction('generate-png', REPORTING_TRANSACTION_TYPE); - if (!options.layout?.dimensions) { - throw new Error(`LayoutParams.Dimensions is undefined.`); - } - - const apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', 'setup'); - let apmBuffer: typeof apm.currentSpan; - - return getScreenshots({ - ...options, - format: 'png', - layout: { id: 'preserve_layout', ...options.layout }, - }).pipe( - tap(({ metrics }) => { - if (metrics) { - apmTrans.setLabel('cpu', metrics.cpu, false); - apmTrans.setLabel('memory', metrics.memory, false); - } - apmScreenshots?.end(); - apmBuffer = apmTrans.startSpan('get-buffer', 'output') ?? null; - }), - map(({ metrics, results }) => ({ - metrics, - buffer: results[0].screenshots[0].data, - warnings: results.reduce((found, current) => { - if (current.error) { - found.push(current.error.message); - } - if (current.renderErrors) { - found.push(...current.renderErrors); - } - return found; - }, [] as string[]), - })), - tap(({ buffer }) => { - logger.debug(`PNG buffer byte length: ${buffer.byteLength}`); - apmTrans.setLabel('byte-length', buffer.byteLength, false); - }), - finalize(() => { - apmBuffer?.end(); - apmTrans.end(); - }) - ); -} diff --git a/packages/kbn-reporting/export_types/png/png_v2.test.ts b/packages/kbn-reporting/export_types/png/png_v2.test.ts index a6cc8b1891eef..bd59306af8168 100644 --- a/packages/kbn-reporting/export_types/png/png_v2.test.ts +++ b/packages/kbn-reporting/export_types/png/png_v2.test.ts @@ -15,12 +15,9 @@ import type { LocatorParams } from '@kbn/reporting-common/types'; import type { TaskPayloadPNGV2 } from '@kbn/reporting-export-types-png-common'; import { createMockConfigSchema } from '@kbn/reporting-mocks-server'; import { cryptoFactory } from '@kbn/reporting-server'; -import type { ScreenshottingStart } from '@kbn/screenshotting-plugin/server'; - +import { createMockScreenshottingStart } from '@kbn/screenshotting-plugin/server/mock'; +import type { CaptureResult } from '@kbn/screenshotting-plugin/server/screenshots'; import { PngExportType } from '.'; -import { generatePngObservable } from './generate_png'; - -jest.mock('./generate_png'); let content: string; let mockPngExportType: PngExportType; @@ -34,49 +31,51 @@ const encryptHeaders = async (headers: Record) => { const crypto = cryptoFactory(mockEncryptionKey); return await crypto.encrypt(headers); }; +let encryptedHeaders: string; +const screenshottingMock = createMockScreenshottingStart(); +const getScreenshotsSpy = jest.spyOn(screenshottingMock, 'getScreenshots'); +const testContent = 'raw string from get_screenhots'; const getBasePayload = (baseObj: unknown) => baseObj as TaskPayloadPNGV2; beforeEach(async () => { content = ''; stream = { write: jest.fn((chunk) => (content += chunk)) } as unknown as typeof stream; - const configType = createMockConfigSchema({ - encryptionKey: mockEncryptionKey, - queue: { - indexInterval: 'daily', - timeout: Infinity, - }, - }); - + const configType = createMockConfigSchema({ encryptionKey: mockEncryptionKey }); const context = coreMock.createPluginInitializerContext(configType); const mockCoreSetup = coreMock.createSetup(); const mockCoreStart = coreMock.createStart(); + encryptedHeaders = await encryptHeaders({}); + mockPngExportType = new PngExportType(mockCoreSetup, configType, mockLogger, context); mockPngExportType.setup({ basePath: { set: jest.fn() }, }); mockPngExportType.start({ + esClient: elasticsearchServiceMock.createClusterClient(), savedObjects: mockCoreStart.savedObjects, uiSettings: mockCoreStart.uiSettings, - screenshotting: {} as unknown as ScreenshottingStart, - esClient: elasticsearchServiceMock.createClusterClient(), + screenshotting: screenshottingMock, }); -}); -afterEach(() => (generatePngObservable as jest.Mock).mockReset()); + getScreenshotsSpy.mockImplementation(() => { + return Rx.of({ + metrics: { cpu: 0 }, + results: [{ screenshots: [{ data: Buffer.from(testContent) }] }] as CaptureResult['results'], + }); + }); +}); test(`passes browserTimezone to generatePng`, async () => { - const encryptedHeaders = await encryptHeaders({}); - (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('') })); - const browserTimezone = 'UTC'; await mockPngExportType.runTask( 'pngJobId', getBasePayload({ forceNow: 'test', + layout: { dimensions: {} }, locatorParams: [], browserTimezone, headers: encryptedHeaders, @@ -85,25 +84,24 @@ test(`passes browserTimezone to generatePng`, async () => { stream ); - expect(generatePngObservable).toHaveBeenCalledWith( - expect.anything(), - expect.anything(), - expect.objectContaining({ - browserTimezone: 'UTC', - headers: {}, - layout: { id: 'preserve_layout' }, - }) - ); + expect(getScreenshotsSpy).toHaveBeenCalledWith({ + format: 'png', + headers: {}, + layout: { dimensions: {}, id: 'preserve_layout' }, + urls: [ + [ + 'http://localhost:80/mock-server-basepath/app/reportingRedirect?forceNow=test', + { __REPORTING_REDIRECT_LOCATOR_STORE_KEY__: undefined }, + ], + ], + }); }); test(`returns content_type of application/png`, async () => { - const encryptedHeaders = await encryptHeaders({}); - - (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from('foo') })); - const { content_type: contentType } = await mockPngExportType.runTask( 'pngJobId', getBasePayload({ + layout: { dimensions: {} }, locatorParams: [{ version: 'test', id: 'test' }] as LocatorParams[], headers: encryptedHeaders, }), @@ -114,13 +112,10 @@ test(`returns content_type of application/png`, async () => { }); test(`returns content of generatePng getBuffer base64 encoded`, async () => { - const testContent = 'raw string from get_screenhots'; - (generatePngObservable as jest.Mock).mockReturnValue(Rx.of({ buffer: Buffer.from(testContent) })); - - const encryptedHeaders = await encryptHeaders({}); await mockPngExportType.runTask( 'pngJobId', getBasePayload({ + layout: { dimensions: {} }, locatorParams: [{ version: 'test', id: 'test' }] as LocatorParams[], headers: encryptedHeaders, }), diff --git a/packages/kbn-reporting/export_types/png/png_v2.ts b/packages/kbn-reporting/export_types/png/png_v2.ts index 15f0c26d45932..2b824bde18a0b 100644 --- a/packages/kbn-reporting/export_types/png/png_v2.ts +++ b/packages/kbn-reporting/export_types/png/png_v2.ts @@ -38,9 +38,7 @@ import { PNG_REPORT_TYPE_V2, TaskPayloadPNGV2, } from '@kbn/reporting-export-types-png-common'; -import { decryptJobHeaders, getFullRedirectAppUrl, ExportType } from '@kbn/reporting-server'; - -import { generatePngObservable } from './generate_png'; +import { decryptJobHeaders, ExportType, getFullRedirectAppUrl } from '@kbn/reporting-server'; export class PngExportType extends ExportType { id = PNG_REPORT_TYPE_V2; @@ -88,14 +86,14 @@ export class PngExportType extends ExportType cancellationToken: CancellationToken, stream: Writable ) => { - const jobLogger = this.logger.get(`execute-job:${jobId}`); + const logger = this.logger.get(`execute-job:${jobId}`); const apmTrans = apm.startTransaction('execute-job-pdf-v2', REPORTING_TRANSACTION_TYPE); const apmGetAssets = apmTrans.startSpan('get-assets', 'setup'); let apmGeneratePng: { end: () => void } | null | undefined; const { encryptionKey } = this.config; const process$: Observable = of(1).pipe( - mergeMap(() => decryptJobHeaders(encryptionKey, payload.headers, jobLogger)), + mergeMap(() => decryptJobHeaders(encryptionKey, payload.headers, logger)), mergeMap((headers) => { const url = getFullRedirectAppUrl( this.config, @@ -108,22 +106,57 @@ export class PngExportType extends ExportType apmGetAssets?.end(); apmGeneratePng = apmTrans.startSpan('generate-png-pipeline', 'execute'); + const options = { + headers, + browserTimezone: payload.browserTimezone, + layout: { ...payload.layout, id: 'preserve_layout' as const }, + }; - return generatePngObservable( - () => - this.startDeps.screenshotting!.getScreenshots({ - format: 'png', - headers, - layout: { ...payload.layout, id: 'preserve_layout' }, - urls: [[url, { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: locatorParams }]], - }), - jobLogger, - { + if (!options.layout?.dimensions) { + throw new Error(`LayoutParams.Dimensions is undefined.`); + } + + const apmScreenshots = apmTrans?.startSpan('screenshots-pipeline', 'setup'); + let apmBuffer: typeof apm.currentSpan; + + return this.startDeps + .screenshotting!.getScreenshots({ + format: 'png', headers, - browserTimezone: payload.browserTimezone, layout: { ...payload.layout, id: 'preserve_layout' }, - } - ); + urls: [[url, { [REPORTING_REDIRECT_LOCATOR_STORE_KEY]: locatorParams }]], + }) + .pipe( + tap(({ metrics }) => { + if (metrics) { + apmTrans.setLabel('cpu', metrics.cpu, false); + apmTrans.setLabel('memory', metrics.memory, false); + } + apmScreenshots?.end(); + apmBuffer = apmTrans.startSpan('get-buffer', 'output') ?? null; + }), + map(({ metrics, results }) => ({ + metrics, + buffer: results[0].screenshots[0].data, + warnings: results.reduce((found, current) => { + if (current.error) { + found.push(current.error.message); + } + if (current.renderErrors) { + found.push(...current.renderErrors); + } + return found; + }, [] as string[]), + })), + tap(({ buffer }) => { + logger.debug(`PNG buffer byte length: ${buffer.byteLength}`); + apmTrans.setLabel('byte-length', buffer.byteLength, false); + }), + finalize(() => { + apmBuffer?.end(); + apmTrans.end(); + }) + ); }), tap(({ buffer }) => stream.write(buffer)), map(({ metrics, warnings }) => ({ @@ -131,7 +164,7 @@ export class PngExportType extends ExportType metrics: { png: metrics }, warnings, })), - tap({ error: (error) => jobLogger.error(error) }), + tap({ error: (error) => logger.error(error) }), finalize(() => apmGeneratePng?.end()) ); diff --git a/packages/kbn-reporting/export_types/png/tsconfig.json b/packages/kbn-reporting/export_types/png/tsconfig.json index bac09188079b5..b8a141a320dd8 100644 --- a/packages/kbn-reporting/export_types/png/tsconfig.json +++ b/packages/kbn-reporting/export_types/png/tsconfig.json @@ -23,6 +23,5 @@ "@kbn/reporting-export-types-png-common", "@kbn/core", "@kbn/reporting-mocks-server", - "@kbn/logging", ] } From 153dcf4bc8cdd154c6d2a6df0a1e5323637c9481 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Wed, 10 Jan 2024 18:01:57 +0100 Subject: [PATCH 07/12] Stabilize index templates and cases navigation tests for MKI (#174501) ## Summary This PR stabilizes the index templates and cases navigation FTR test suites for MKI runs on the security project. ### Details - The index templates tests was failing on a retried step, because the cleanup of `TEST_TEMPLATE_NAME` didn't work as intended. Changing the name to include the date as "random" part instead of a random floating point number fixes the cleanup. - The navigation to the cases app in a security project sometimes failed with error that look like incomplete loading of assets. This PR wraps this navigation step in a retry and adds some waits for global loading between navigation and assertion. As part of that, the security `navigateToLandingPage` has been adjusted to check for an actual object on the landing page. --- .../functional/services/svl_sec_navigation.ts | 5 +---- .../index_management/index_templates.ts | 2 +- .../test_suites/security/ftr/navigation.ts | 18 +++++++++++++----- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/x-pack/test_serverless/functional/services/svl_sec_navigation.ts b/x-pack/test_serverless/functional/services/svl_sec_navigation.ts index 85f21940d5ab1..c990258cd8060 100644 --- a/x-pack/test_serverless/functional/services/svl_sec_navigation.ts +++ b/x-pack/test_serverless/functional/services/svl_sec_navigation.ts @@ -19,10 +19,7 @@ export function SvlSecNavigationServiceProvider({ async navigateToLandingPage() { await retry.tryForTime(60 * 1000, async () => { await PageObjects.common.navigateToApp('landingPage'); - // Currently, the security landing page app is not loading correctly. - // Replace '~kbnAppWrapper' with a proper test subject of the landing - // page once it loads successfully. - await testSubjects.existOrFail('~kbnAppWrapper', { timeout: 2000 }); + await testSubjects.existOrFail('welcome-header', { timeout: 2000 }); }); }, }; diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_templates.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_templates.ts index 371ee4debe98f..6092473ad27bc 100644 --- a/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_templates.ts +++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_templates.ts @@ -75,7 +75,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); describe('Create index template', () => { - const TEST_TEMPLATE_NAME = `test_template_${Math.random()}`; + const TEST_TEMPLATE_NAME = `test_template_${Date.now()}`; after(async () => { await es.indices.deleteIndexTemplate({ name: TEST_TEMPLATE_NAME }, { ignore: [404] }); diff --git a/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts b/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts index e3c8de72570b5..69bc9d517e7e8 100644 --- a/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/security/ftr/navigation.ts @@ -16,6 +16,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { const svlCommonNavigation = getPageObject('svlCommonNavigation'); const testSubjects = getService('testSubjects'); const browser = getService('browser'); + const headerPage = getPageObject('header'); + const retry = getService('retry'); describe('navigation', function () { before(async () => { @@ -49,6 +51,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await svlCommonNavigation.search.searchFor('security dashboards'); await svlCommonNavigation.search.clickOnOption(0); await svlCommonNavigation.search.hideSearch(); + await headerPage.waitUntilLoadingHasFinished(); await expect(await browser.getCurrentUrl()).contain('app/security/dashboards'); }); @@ -63,12 +66,17 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { }); it('navigates to cases app', async () => { - await svlCommonNavigation.sidenav.clickLink({ - deepLinkId: 'securitySolutionUI:cases' as AppDeepLinkId, - }); + await retry.tryForTime(30 * 1000, async () => { + // start navigation to the cases app from the landing page + await svlSecNavigation.navigateToLandingPage(); + await svlCommonNavigation.sidenav.clickLink({ + deepLinkId: 'securitySolutionUI:cases' as AppDeepLinkId, + }); + await headerPage.waitUntilLoadingHasFinished(); - expect(await browser.getCurrentUrl()).contain('/app/security/cases'); - await testSubjects.existOrFail('cases-all-title'); + expect(await browser.getCurrentUrl()).contain('/app/security/cases'); + await testSubjects.existOrFail('cases-all-title'); + }); }); }); } From f1415345bac26e594ab76fa223f0b439e1920be8 Mon Sep 17 00:00:00 2001 From: "Joey F. Poon" Date: Wed, 10 Jan 2024 09:21:10 -0800 Subject: [PATCH 08/12] [Security Solution] unskip endpoint alerts cypress tests (#174493) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Unskip endpoint alerts cypress test. Various enhancements to our tests were previously made, this is just retroactively unskipping after verifying with flaky test runner. Flaky test runs: - https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/4798 (50x ✅ ) ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../public/management/cypress/e2e/endpoint_alerts.cy.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts index e4f913d851735..06b33141bad1b 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/endpoint_alerts.cy.ts @@ -19,8 +19,7 @@ import { login, ROLE } from '../tasks/login'; import { EXECUTE_ROUTE } from '../../../../common/endpoint/constants'; import { waitForActionToComplete } from '../tasks/response_actions'; -// FLAKY: https://github.com/elastic/kibana/issues/169958 -describe.skip('Endpoint generated alerts', { tags: ['@ess', '@serverless'] }, () => { +describe('Endpoint generated alerts', { tags: ['@ess', '@serverless'] }, () => { let indexedPolicy: IndexedFleetEndpointPolicyResponse; let policy: PolicyData; let createdHost: CreateAndEnrollEndpointHostResponse; From ed7c6477ced43d639eecb339ee4f5bf74026d6de Mon Sep 17 00:00:00 2001 From: amyjtechwriter <61687663+amyjtechwriter@users.noreply.github.com> Date: Wed, 10 Jan 2024 17:23:55 +0000 Subject: [PATCH 09/12] [DOCS] Adds the 8.11.4 release notes (#174636) Adds the 8.11.4 release notes (there aren't any for Kibana in this release). --- docs/CHANGELOG.asciidoc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 63b13250a1591..f7fc138caf4eb 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,7 @@ Review important information about the {kib} 8.x releases. +* <> * <> * <> * <> @@ -56,6 +57,15 @@ Review important information about the {kib} 8.x releases. * <> -- + +[[release-notes-8.11.4]] +== {kib} 8.11.4 + +[float] +[[fixes-v8.11.4]] +=== Bug fixes and enhancements +There are no user-facing changes in the 8.11.4 release. + [[release-notes-8.11.3]] == {kib} 8.11.3 From 671b854a85731fbd24df6ff599e057a8e60bf723 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 10 Jan 2024 12:01:16 -0600 Subject: [PATCH 10/12] [ci/flaky_test_runner] Cancel on failing build (#174314) This cancels the build if there's a test failure on a flaky test run. We can assume the suite is still flaky if there are failures. Co-authored-by: Brad White --- .buildkite/pipelines/flaky_tests/pipeline.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.buildkite/pipelines/flaky_tests/pipeline.ts b/.buildkite/pipelines/flaky_tests/pipeline.ts index f07d016af8dea..34f6e41297861 100644 --- a/.buildkite/pipelines/flaky_tests/pipeline.ts +++ b/.buildkite/pipelines/flaky_tests/pipeline.ts @@ -143,11 +143,9 @@ for (const testSuite of testSuites) { }, depends_on: 'build', timeout_in_minutes: 150, + cancel_on_build_failing: true, retry: { - automatic: [ - { exit_status: '-1', limit: 3 }, - // { exit_status: '*', limit: 1 }, - ], + automatic: [{ exit_status: '-1', limit: 3 }], }, }); continue; @@ -173,6 +171,10 @@ for (const testSuite of testSuites) { concurrency, concurrency_group: process.env.UUID, concurrency_method: 'eager', + cancel_on_build_failing: true, + retry: { + automatic: [{ exit_status: '-1', limit: 3 }], + }, env: { // disable split of test cases between parallel jobs when running them in flaky test runner // by setting chunks vars to value 1, which means all test will run in one job From 947f25ffa1df07b971ba62a7d122635d66b6e736 Mon Sep 17 00:00:00 2001 From: amyjtechwriter <61687663+amyjtechwriter@users.noreply.github.com> Date: Wed, 10 Jan 2024 18:10:37 +0000 Subject: [PATCH 11/12] [DOCS] Adds 8.12.0 release notes (#174429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the 8.12.0 release notes --------- Co-authored-by: István Zoltán Szabó Co-authored-by: Jeramy Soucy Co-authored-by: Matthias Wilhelm Co-authored-by: Stratoula Kalafateli --- docs/CHANGELOG.asciidoc | 260 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index f7fc138caf4eb..7391002139d2e 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,7 @@ Review important information about the {kib} 8.x releases. +* <> * <> * <> * <> @@ -57,6 +58,264 @@ Review important information about the {kib} 8.x releases. * <> -- +[[release-notes-8.12.0]] +== {kib} 8.12.0 + +For information about the {kib} 8.12.0 release, review the following information. + +[float] +[[breaking-changes-8.12.0]] +=== Breaking changes + +Breaking changes can prevent your application from optimal operation and performance. +Before you upgrade to 8.12.0, review the breaking changes, then mitigate the impact to your application. + +[discrete] +[[breaking-172224]] +.New SLO architecture +[%collapsible] +==== +*Details* + +We introduce a breaking change in the SLO features that will break any SLOs created before 8.12. These SLOs have to be manually reset through an API until we provide a UI for it. The data aggregated over time (rollup) is still available in the sli v2 index, but won't be used for summary calculation when reset. + +The previous summary transforms summarizing every SLOs won't be used anymore and can be stopped and deleted: + +* slo-summary-occurrences-7d-rolling +* slo-summary-occurrences-30d-rolling +* slo-summary-occurrences-90d-rolling +* slo-summary-occurrences-monthly-aligned +* slo-summary-occurrences-weekly-aligned +* slo-summary-timeslices-7d-rolling +* slo-summary-timeslices-30d-rolling +* slo-summary-timeslices-90d-rolling +* slo-summary-timeslices-monthly-aligned +* slo-summary-timeslices-weekly-aligned + +Be aware that when installing a new SLO (or after resetting an SLO), we install two transforms (one for the rollup data and one that summarize the rollup data). Do not delete the new `slo-summary-{slo_id}-{slo_revision}` transforms. For more information, refer to ({kibana-pull}172224[#172224]). +==== + +[discrete] +[[breaking-170635]] +.A new sub-feature privilege to control user access to the cases settings +[%collapsible] +==== +*Details* + +Roles with at least a sub-feature privilege configured will not have access to the cases setting like they had previously. All roles without a sub-feature privilege configured will not be affected. For more information, refer to ({kibana-pull}170635[#170635]). +==== + +[float] +[[features-8.12.0]] +=== Features +{kib} 8.12.0 adds the following new and notable features. + +Alerting:: +* The case list filter bar is now customizable, filters are removable and custom fields can be used as filters ({kibana-pull}172276[#172276]). +APM:: +* Adds viewInApp URL to the custom threshold rule type ({kibana-pull}171985[#171985]). +* Adds back the mobile crashes & errors tab ({kibana-pull}165892[#165892]). +Elastic Security:: +For the Elastic Security 8.12.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Elastic Search:: +* Display E5 multilingual callout ({kibana-pull}171887[#171887]). +* Replace model selection dropdown with list ({kibana-pull}171436[#171436]). +Fleet:: +* Adds support for preconfigured output secrets (Scrypt edition) ({kibana-pull}172041[#172041]). +* Adds UI components to create and edit output secrets ({kibana-pull}169429[#169429]). +* Adds support for remote ES output ({kibana-pull}169252[#169252]). +* Adds the ability to specify secrets in outputs ({kibana-pull}169221[#169221]). +* Adds an integrations configs tab to display input templates ({kibana-pull}168827[#168827]). +* Adds a {kib} task to publish Agent metrics ({kibana-pull}168435[#168435]). +Lens & Visualizations:: +* Adds the ability to edit charts made by {esql} queries in Dashboard ({kibana-pull}169911[#169911]). +Machine Learning:: +* Adds E5 model configurations ({kibana-pull}172053[#172053]). +* Adds the ability to create a categorization anomaly detection job from pattern analysis ({kibana-pull}170567[#170567]). +* Adds and displays alerts data in the Anomaly Explorer ({kibana-pull}167998[#167998]). +Observability:: +* Adds logic to update flyout highlights ({kibana-pull}172193[#172193]). +* Adds logic to display highlights in the flyout ({kibana-pull}170650[#170650]). +* Changes the Custom threshold title to Beta ({kibana-pull}172360[#172360]). +Security:: +* Disables the connector parameters field ({kibana-pull}173610[#173610]). +* Adds a risk engine missing privileges callout ({kibana-pull}171250[#171250]). +* Asset criticality privileges API ({kibana-pull}172441[#172441]). +Uptime:: +* Global params Public APIs ({kibana-pull}169669[#169669]). +* Private location public API's ({kibana-pull}169376[#169376]). +* Settings public API ({kibana-pull}163400[#163400]). + +For more information about the features introduced in 8.12.0, refer to <>. + +[[enhancements-and-bug-fixes-v8.12.0]] +=== Enhancements and bug fixes + +For detailed information about the 8.12.0 release, review the enhancements and bug fixes. + +[float] +[[enhancement-v8.12.0]] +=== Enhancements +Alerting:: +* Auto close ServiceNow incidents when alerts are resolved ({kibana-pull}171760[#171760]). +* PagerDuty connector now supports the links and `custom_details` attributes ({kibana-pull}171748[#171748]). +* Adds a mute and unmute action component in the alerts table row actions ({kibana-pull}170651[#170651]). +* Extends the PagerDuty connector API to support the `links` and `custom_details` attributes provided by the Event API ({kibana-pull}170459[#170459]). +* Adds toggle for alert as data fields in alert templating ({kibana-pull}170162[#170162]). +APM:: +* Perform functions and LLM interactions on the server ({kibana-pull}172590[#172590]). +* Adds viewInApp URL to the custom threshold rule type ({kibana-pull}171985[#171985]). +* Adds the KQL bar to embeddables ({kibana-pull}171016[#171016]). +* Enables the average mobile app launch time panel ({kibana-pull}170773[#170773]). +* Enables the mobile most launches panel ({kibana-pull}168925[#168925]). +* Improves the Differential Top N functions grid view ({kibana-pull}170008[#170008]). +Cases:: +* Users can copy to the clipboard the hashes of files uploaded to cases ({kibana-pull}172450[#172450]). +* Allow users to configure which columns are displayed in the cases list including custom fields ({kibana-pull}170950[#170950]). +* Adds a new sub-feature privilege to control user access to the cases settings ({kibana-pull}170635[#170635]). +Dashboard:: +* Adds Links to the Visualization library ({kibana-pull}170810[#170810]). +Discover:: +* Adds a field tokens column in the grid header ({kibana-pull}167179[#167179]). +* Enables the addition of columns from the document viewer when using ES|QL ({kibana-pull}171083[#171083]). +* Adds field search via wildcards in the document viewer ({kibana-pull}168616[#168616]). +* Improves search for field names by handling spaces like wildcards ({kibana-pull}168381[#168381]). +* Updates mapping conflict popover with types list ({kibana-pull}169855[#169855]). +* On search source error, show 'view details' action that opens request in inspector ({kibana-pull}170790[#170790]). +* Adds an Unsaved changes label when in an unsaved state of saved search ({kibana-pull}169548[#169548]). +* Allows changing the current sample size and saving it with a saved search ({kibana-pull}157269[#157269]). +* Adds new sparse vector and dense vector icons ({kibana-pull}169493[#169493]). +* Adds `sparse_vector` field support ({kibana-pull}168186[#168186]). +Elastic Security:: +For the Elastic Security 8.12.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Elastic Search:: +* Split details panel from model selection list ({kibana-pull}173434[#173434]). +Fleet:: +* Adds support for Elasticsearch output performance presets ({kibana-pull}172359[#172359]). +* Adds a new `keep_monitoring_alive` flag to agent policies ({kibana-pull}168865[#168865]). +* Adds support for additional types for dynamic mappings ({kibana-pull}168842[#168842]). +* Implements Elastic Agent upgrade states UI ({kibana-pull}167539[#167539]). +* Use default component templates from Elasticsearch ({kibana-pull}163731[#163731]). +Lens & Visualizations:: +* Moves the tagcloud visualization in *Lens* out of experimental status ({kibana-pull}168824[#168824]). +* Allows coloring an entire metric panel when applying a maximum value to the metric visualization in **Lens** ({kibana-pull}172531[#172531]). +* Adds truncation for data view pickers and field lists with many characters ({kibana-pull}172296[#172296]). +* Allows searching in the {esql} inline documentation description ({kibana-pull}171916[#171916]). +* Allows setting non-numeric metrics for metric visualizations in *Lens* ({kibana-pull}169258[#169258]). +Machine Learning:: +* Removes the beta badge from ML alerting rules ({kibana-pull}173545[#173545]). +* Removes the technical preview badge from AIOps log rate analysis ({kibana-pull}172722[#172722]). +* Adds anomaly description as an alert message for the anomaly detection rule type ({kibana-pull}172473[#172473]). +* Adds a sampled percentage of documents, and cardinality, for text fields for the Data Visualizer Field statistics tab and addresses an issue with a missing bucket in the document count chart ({kibana-pull}172378[#172378]). +* Adds option to display an overlay chart on the data drift expanded row ({kibana-pull}172239[#172239]). +* AIOps: Shows top N results when no documents are in baseline or deviation in log rate analysis({kibana-pull}171924[#171924]). +* AIOps: Adds support to restore baseline and deviation from URL state on page refresh for log rate analysis ({kibana-pull}171398[#171398]). +* Validates and limits threading parameters for starting a model deployment ({kibana-pull}171921[#171921]). +* Trained models: Adds a missing job node to models map view when original job has been deleted ({kibana-pull}171590[#171590]). +* Trained models list: Disables the View training data action if data frame analytics job no longer exists ({kibana-pull}171061[#171061]). +* Adds a trained model flyout with available models to download for in the Trained Models UI ({kibana-pull}171024[#171024]). +* Allows temporary data views in the anomaly detection jobs wizards ({kibana-pull}170112[#170112]). +* Assigns downloaded ELSER models to the `*` space ({kibana-pull}169939[#169939]). +* Adds pattern analysis to the anomaly action menu ({kibana-pull}169400[#169400]). +* Adds test pipeline action for data frame analysis trained models in models list ({kibana-pull}168400[#168400]). +Management:: +* Adds a search bar to the Clusters and shards tab ({kibana-pull}171806[#171806]). +* Aligns data view and destination index creation workflows in Transforms and Data Frame Analytics wizards ({kibana-pull}171202[#171202]). +* The index lifecycle summary on the Index lifecycle page is now displayed in a separate tab ({kibana-pull}170726[#170726]). +* Adds the ability to view mappings conflicts in data views on the data view management page ({kibana-pull}169381[#169381]). +* Implements index overview cards ({kibana-pull}168153[#168153]). +Observability:: +* Reset UI for updating outdated SLOs ({kibana-pull}172883[#172883]). +* Adds timeslice metric indicator for SLOs ({kibana-pull}168539[#168539]). +* Adds logic to update flyout highlights ({kibana-pull}172193[#172193]). +* Sets budget consumed mode as the default mode for burn rate rule configuration ({kibana-pull}171433[#171433]). +* Allow users to define burn rate windows using budget consumed ({kibana-pull}170996[#170996]). +* Makes rules created in Discover visible in Observability ({kibana-pull}171364[#171364]). +* Adds support for document count to custom metric indicator ({kibana-pull}170913[#170913]). +* Improves displaying inline frames ({kibana-pull}169212[#169212]). +* Adds summary insight to the Differential flamegraph ({kibana-pull}168978[#168978]). +* Include `search-*` when recalling documents ({kibana-pull}173710[#173710]). +Platform:: +* Limits `elasticsearch.maxSockets` to 800 by default ({kibana-pull}151911[#151911]). +Presentation:: +* Adds popover message in the control title ({kibana-pull}172094[#172094]). +* Displays incomplete results warning in layer legend ({kibana-pull}171144[#171144]). +* Updates incomplete data messaging ({kibana-pull}169578[#169578]). +Reporting:: +* Makes searches used for CSV export inspectable ({kibana-pull}171248[#171248]). +* Adds `max_concurrent_shards` setting to schema for the point in time CSV report generation ({kibana-pull}170344[#170344]). +Security:: +* The default value of the `elasticsearch.requestHeadersWhitelist` configuration option has been expanded to include the `es-client-authentication` HTTP header, in addition to `authorization` ({kibana-pull}172444[#172444]). +* Adds risk engine missing privileges callout ({kibana-pull}171250[#171250]). +* Implements Asset Criticality Create, Read & Delete APIs ({kibana-pull}172073[#172073]). + +[float] +[[fixes-v8.12.0]] +=== Bug Fixes +Alerting:: +* Fixes the alert details page search bar not considering Query configurations in the {kib} advanced settings ({kibana-pull}172498[#172498]). +* Fixes adding evaluation threshold to alert payload for ES query rule ({kibana-pull}171571[#171571]). +* Hides the Logs tab in Rules page to unauthorized users ({kibana-pull}171417[#171417]). +* Fixes hyperlinks in Slack messages being broken when there is "_" or "*" in the URL ({kibana-pull}170067[#170067]). +APM:: +* Removes usage of internal client when fetching agent configuration etags metrics ({kibana-pull}173001[#173001]). +* Fixes encoding custom links values ({kibana-pull}171032[#171032]). +* Fixes an issue where data views were previously not space aware ({kibana-pull}170857[#170857]). +* Fixes issue with onboarding page around java agent ({kibana-pull}168816[#168816]). +* Adds a data tier filter to the `/has_data` API ({kibana-pull}173382[#173382]). +Cases:: +* Fixes a bug that prevented users with read permission from being assigned to cases ({kibana-pull}172047[#172047]). +Dashboard:: +* Prevents unnecessary loss of dashboard unsaved state ({kibana-pull}167707[#167707]). +Discover:: +* Fixes escaping column names when copying ({kibana-pull}170997[#170997]). +* Discover sharing links now preserve customized column widths ({kibana-pull}172405[#172405]). +* Fixes displaying the columns as they are returned from the query ({kibana-pull}171874[#171874]). +* Fixes issue with `defaultColumns` when changing data views ({kibana-pull}168994[#168994]). +Elastic Security:: +For the Elastic Security 8.12.0 release information, refer to {security-guide}/release-notes.html[_Elastic Security Solution Release Notes_]. +Fleet:: +* Allows agent upgrades if patch version is higher than {kib} ({kibana-pull}173167[#173167]). +* Fixes secrets with dot-separated variable names ({kibana-pull}173115[#173115]). +* Fixes endpoint privilege management endpoints return errors ({kibana-pull}171722[#171722]). +* Fixes expiration time for immediate bulk upgrades being too short ({kibana-pull}170879[#170879]). +* Fixes incorrect overwrite of `logs-*` and `metrics-*` data views on every integration install ({kibana-pull}170188[#170188]). +* Creates intermediate objects when using dynamic mappings ({kibana-pull}169981[#169981]). +Lens & Visualizations:: +* Fixes the sorting of null values so they are displayed last ({kibana-pull}172691[#172691]). +* Fixes the overwriting of chart descriptions after editing a visualization in *Lens* ({kibana-pull}172653[#172653]). +* Fixes an issue where the timerange panel wasn't correctly assigned during a conversion from dashboard to *Lens* ({kibana-pull}172647[#172647]). +* Various fixes for heatmap in *Lens* ({kibana-pull}172602[#172602]). +* Fixes filters being lost when navigating from dashboard -> editor -> *Lens* in *TSVB* ({kibana-pull}172566[#172566]). +* Ignore drop ES|QL commands for date histogram in discover ({kibana-pull}171769[#171769]). +Machine Learning:: +* Ensures data frame analytics job can be deleted from analytics map ({kibana-pull}174212[#174212]). +* Fixes filter for boolean fields filtering for numbers in Field statistics / Data Visualizer ({kibana-pull}174212[#174050]) +* Fixes registering of the ML alerting rules with the basic license ({kibana-pull}173644[#173644]). +* Fixes display of actions column in the datafeed chart flyout ({kibana-pull}173365[#173365]). +* Fixes View in Discover option in Anomaly explorer not handling multiple field values or values with quotation marks ({kibana-pull}172897[#172897]). +* Fixes field stats in Discover showing 0 sample count at times when switching data views ({kibana-pull}172734[#172734]). +* Fixes long field names overflowing in Anomaly detection wizard detector selection ({kibana-pull}172715[#172715]). +* Fixes data drift numeric fields not showing correctly ({kibana-pull}172504[#172504]). +* Fixes Data Visualizer / ML field stats and Data Frame Analytics to exclude _tier fields ({kibana-pull}172223[#172223]). +* Uses standard analyzer in log pattern analysis to ensure filter in Discover matches correct documents ({kibana-pull}172188[#172188]). +* Fixes ML node check and checks user privileges to create job button in dashboard ({kibana-pull}172022[#172022]). +* Fixes {kib} object list in new job from recognized index page ({kibana-pull}171935[#171935]). +Management:: +* Fixes retention policy field name not setting by default correctly in the Transform creation wizard ({kibana-pull}172609[#172609]). +Metrics:: +* Moves formulas and dashboard config to inventory models ({kibana-pull}171872[#171872]). +Platform:: +* Fixes a bug that could cause the `rollingFile` log appender to not properly rotate files on DST switch days ({kibana-pull}173811[#173811]). +* Fixes context formula functions ({kibana-pull}172710[#172710]). +Observability:: +* Removes legacy screenshot image data from the codepath in Synthetics ({kibana-pull}172684[#172684]). +* Fixes incorrect rule parameters when changing aggregation type using a custom equation ({kibana-pull}171958[#171958]). +* Adds parent link in host detail's breadcrumb ({kibana-pull}170792[#170792]). +Presentation:: +* Fixes validation query for nested fields ({kibana-pull}173690[#173690]). +* Fixes user privileges around Links panels saved to the library ({kibana-pull}173332[#173332]). +* Prevents overflowing dashboard title on saved toast notifications ({kibana-pull}172620[#172620]). +* Ignore indices without geometry field in vector tile requests ({kibana-pull}171472[#171472]). +* Fixes layer displaying no data instead of error ({kibana-pull}170084[#170084]). [[release-notes-8.11.4]] == {kib} 8.11.4 @@ -66,6 +325,7 @@ Review important information about the {kib} 8.x releases. === Bug fixes and enhancements There are no user-facing changes in the 8.11.4 release. + [[release-notes-8.11.3]] == {kib} 8.11.3 From 562b671a5d602875c1a7b3217f9d68a1380a926d Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Wed, 10 Jan 2024 10:40:27 -0800 Subject: [PATCH 12/12] [Cases] Automate serverless security screenshots (#174556) --- .buildkite/ftr_configs.yml | 1 + .../security/config.screenshots.ts | 18 ++++ .../security/screenshot_creation/index.ts | 14 +++ .../response_ops_docs/cases/index.ts | 20 ++++ .../response_ops_docs/cases/list_view.ts | 96 +++++++++++++++++++ .../response_ops_docs/cases/testfile.png | 0 .../response_ops_docs/index.ts | 30 ++++++ 7 files changed, 179 insertions(+) create mode 100644 x-pack/test_serverless/functional/test_suites/security/config.screenshots.ts create mode 100644 x-pack/test_serverless/functional/test_suites/security/screenshot_creation/index.ts create mode 100644 x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/index.ts create mode 100644 x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/list_view.ts create mode 100644 x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/testfile.png create mode 100644 x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/index.ts diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 17a357c71640b..cc574b7a74c85 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -423,6 +423,7 @@ enabled: - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group5.ts - x-pack/test_serverless/functional/test_suites/observability/common_configs/config.group6.ts - x-pack/test_serverless/functional/test_suites/observability/config.screenshots.ts + - x-pack/test_serverless/functional/test_suites/security/config.screenshots.ts - x-pack/test_serverless/functional/test_suites/search/config.ts - x-pack/test_serverless/functional/test_suites/search/config.examples.ts - x-pack/test_serverless/functional/test_suites/search/config.screenshots.ts diff --git a/x-pack/test_serverless/functional/test_suites/security/config.screenshots.ts b/x-pack/test_serverless/functional/test_suites/security/config.screenshots.ts new file mode 100644 index 0000000000000..92a46e01b0e4f --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/config.screenshots.ts @@ -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 { createTestConfig } from '../../config.base'; + +export default createTestConfig({ + serverlessProject: 'security', + testFiles: [require.resolve('./screenshot_creation')], + junit: { + reportName: 'Serverless Security Screenshot Creation', + }, + + esServerArgs: ['xpack.ml.ad.enabled=false', 'xpack.ml.dfa.enabled=false'], +}); diff --git a/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/index.ts b/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/index.ts new file mode 100644 index 0000000000000..ca0d8ab0c191d --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('Screenshots - serverless security UI', function () { + loadTestFile(require.resolve('./response_ops_docs')); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/index.ts b/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/index.ts new file mode 100644 index 0000000000000..c2a17b8e8e82d --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { FtrProviderContext } from '../../../../../ftr_provider_context'; + +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const browser = getService('browser'); + + describe('security cases', function () { + before(async () => { + await browser.setWindowSize(1920, 1080); + }); + + loadTestFile(require.resolve('./list_view')); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/list_view.ts b/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/list_view.ts new file mode 100644 index 0000000000000..7103bae64f984 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/list_view.ts @@ -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 { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common'; +import { CaseSeverity } from '@kbn/cases-plugin/common/types/domain'; +import { FtrProviderContext } from '../../../../../ftr_provider_context'; +import { navigateToCasesApp } from '../../../../../../shared/lib/cases'; + +export default function ({ getPageObject, getPageObjects, getService }: FtrProviderContext) { + const cases = getService('cases'); + const pageObjects = getPageObjects(['common', 'header', 'svlCommonPage']); + const svlCases = getService('svlCases'); + const svlCommonScreenshots = getService('svlCommonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'security_cases']; + const testSubjects = getService('testSubjects'); + const owner = SECURITY_SOLUTION_OWNER; + let caseIdSuspiciousEmail: string; + + describe('list view', function () { + before(async () => { + await svlCases.api.createCase( + svlCases.api.getPostCaseRequest(owner, { + title: 'Unusual processes identified', + tags: ['linux', 'os processes'], + description: 'Test.', + owner, + severity: CaseSeverity.HIGH, + }) + ); + + const caseSuspiciousEmail = await svlCases.api.createCase( + svlCases.api.getPostCaseRequest(owner, { + title: 'Suspicious emails reported', + tags: ['email', 'phishing'], + description: 'Several employees have received suspicious emails from an unknown address.', + owner, + }) + ); + caseIdSuspiciousEmail = caseSuspiciousEmail.id; + + await svlCases.api.createCase( + svlCases.api.getPostCaseRequest(owner, { + title: 'Malware investigation', + tags: ['malware'], + description: 'Test.', + owner, + severity: CaseSeverity.MEDIUM, + }) + ); + }); + + after(async () => { + await svlCases.api.deleteAllCaseItems(); + await pageObjects.svlCommonPage.forceLogout(); + }); + + beforeEach(async () => { + await pageObjects.svlCommonPage.login(); + }); + + it('cases list screenshot', async () => { + await navigateToCasesApp(getPageObject, getService, owner); + await pageObjects.header.waitUntilLoadingHasFinished(); + await svlCommonScreenshots.takeScreenshot('cases-home-page', screenshotDirectories); + }); + + it('case settings screenshot', async () => { + await navigateToCasesApp(getPageObject, getService, owner); + await testSubjects.click('configure-case-button'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await svlCommonScreenshots.takeScreenshot('case-settings', screenshotDirectories); + }); + + it('case detail screenshot', async () => { + await pageObjects.common.navigateToUrlWithBrowserHistory( + 'securitySolution', + `/cases/${caseIdSuspiciousEmail}`, + undefined + ); + await pageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.existOrFail('case-view-title'); + const collapseNav = await testSubjects.find('euiCollapsibleNavButton'); + await collapseNav.click(); + await svlCommonScreenshots.takeScreenshot('cases-ui-open', screenshotDirectories, 1400, 1024); + const filesTab = await testSubjects.find('case-view-tab-title-files'); + await filesTab.click(); + await cases.casesFilesTable.addFile(require.resolve('./testfile.png')); + await testSubjects.getVisibleText('cases-files-name-link'); + await svlCommonScreenshots.takeScreenshot('cases-files', screenshotDirectories, 1400, 1024); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/testfile.png b/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/cases/testfile.png new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/index.ts b/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/index.ts new file mode 100644 index 0000000000000..6e11aad1dab1c --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/security/screenshot_creation/response_ops_docs/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const browser = getService('browser'); + const ml = getService('ml'); + + describe('response ops docs', function () { + this.tags(['responseOps']); + + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.testResources.disableKibanaAnnouncements(); + await browser.setWindowSize(1920, 1080); + }); + + after(async () => { + await ml.testResources.resetKibanaTimeZone(); + await ml.testResources.resetKibanaAnnouncements(); + }); + + loadTestFile(require.resolve('./cases')); + }); +}