diff --git a/.buildkite/pipelines/performance/nightly.yml b/.buildkite/pipelines/performance/nightly.yml index aa52fb7a9335..dfee1061815c 100644 --- a/.buildkite/pipelines/performance/nightly.yml +++ b/.buildkite/pipelines/performance/nightly.yml @@ -24,8 +24,6 @@ steps: agents: queue: ci-group-6 depends_on: build - concurrency: 50 - concurrency_group: 'performance-test-group' - wait: ~ continue_on_failure: true diff --git a/.buildkite/scripts/steps/functional/performance.sh b/.buildkite/scripts/steps/functional/performance.sh index 2f1a77690d15..8e3793733a6e 100644 --- a/.buildkite/scripts/steps/functional/performance.sh +++ b/.buildkite/scripts/steps/functional/performance.sh @@ -14,6 +14,8 @@ cat << EOF | buildkite-agent pipeline upload steps: - command: .buildkite/scripts/steps/functional/performance_sub.sh parallelism: "$ITERATION_COUNT" + concurrency: 20 + concurrency_group: 'performance-test-group' EOF diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000000..31bd82264035 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,58 @@ +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale +daysUntilStale: 180 + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. +daysUntilClose: false + +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +onlyLabels: ["Team:apm"] + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable +exemptLabels: ["technical debt", "prevent stale"] + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: false + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: false + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: false + +# Label to use when marking as stale +staleLabel: stale + +# Comment to post when marking as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Comment to post when closing a stale Issue or Pull Request. +# closeComment: > +# Your comment here. + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 30 + +# Limit to only `issues` or `pulls` +only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +# pulls: +# daysUntilStale: 30 +# markComment: > +# This pull request has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. + +# issues: +# exemptLabels: +# - confirmed diff --git a/dev_docs/tutorials/saved_objects.mdx b/dev_docs/tutorials/saved_objects.mdx index 29a0b60983d9..9583e195d1c8 100644 --- a/dev_docs/tutorials/saved_objects.mdx +++ b/dev_docs/tutorials/saved_objects.mdx @@ -254,4 +254,4 @@ the error should be verbose and informative so that the corrupt document can be ### Testing Migrations -Bugs in a migration function cause downtime for our users and therefore have a very high impact. Follow the . +Bugs in a migration function cause downtime for our users and therefore have a very high impact. Follow the . diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index ed6763db69ff..4e44df9d4e18 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -230,6 +230,7 @@ readonly links: { readonly ingest: Record; readonly fleet: Readonly<{ datastreamsILM: string; + beatsAgentComparison: string; guide: string; fleetServer: string; fleetServerAddFleetServer: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 96c2c0df9d78..5871a84c5402 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly elasticStackGetStarted: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
readonly troubleshootGaps: string;
};
readonly securitySolution: {
readonly trustedApps: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Readonly<{
guide: string;
infrastructureThreshold: string;
logsThreshold: string;
metricsThreshold: string;
monitorStatus: string;
monitorUptime: string;
tlsCertificate: string;
uptimeDurationAnomaly: string;
}>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
elasticsearchEnableApiKeys: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly spaces: Readonly<{
kibanaLegacyUrlAliases: string;
kibanaDisableLegacyUrlAliasesApi: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
datastreamsILM: string;
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
installElasticAgent: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
learnMoreBlog: string;
apiKeysLearnMore: string;
}>;
readonly ecs: {
readonly guide: string;
};
readonly clients: {
readonly guide: string;
readonly goOverview: string;
readonly javaIndex: string;
readonly jsIntro: string;
readonly netGuide: string;
readonly perlGuide: string;
readonly phpGuide: string;
readonly pythonGuide: string;
readonly rubyOverview: string;
readonly rustGuide: string;
};
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly settings: string;
readonly elasticStackGetStarted: string;
readonly apm: {
readonly kibanaSettings: string;
readonly supportedServiceMaps: string;
readonly customLinks: string;
readonly droppedTransactionSpans: string;
readonly upgrading: string;
readonly metaData: string;
};
readonly canvas: {
readonly guide: string;
};
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
readonly suricataModule: string;
readonly zeekModule: string;
};
readonly auditbeat: {
readonly base: string;
readonly auditdModule: string;
readonly systemModule: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly libbeat: {
readonly getStarted: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly search: {
readonly sessions: string;
readonly sessionLimits: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
readonly runtimeFields: string;
};
readonly addData: string;
readonly kibana: string;
readonly upgradeAssistant: string;
readonly rollupJobs: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly privileges: string;
readonly guide: string;
readonly gettingStarted: string;
readonly ml: string;
readonly ruleChangeLog: string;
readonly detectionsReq: string;
readonly networkMap: string;
readonly troubleshootGaps: string;
};
readonly securitySolution: {
readonly trustedApps: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
readonly autocompleteChanges: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
byteSizeUnits: string;
createAutoFollowPattern: string;
createFollower: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createRollupJobsRequest: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
timeUnits: string;
updateTransform: string;
}>;
readonly observability: Readonly<{
guide: string;
infrastructureThreshold: string;
logsThreshold: string;
metricsThreshold: string;
monitorStatus: string;
monitorUptime: string;
tlsCertificate: string;
uptimeDurationAnomaly: string;
}>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
elasticsearchEnableApiKeys: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly spaces: Readonly<{
kibanaLegacyUrlAliases: string;
kibanaDisableLegacyUrlAliasesApi: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
readonly fleet: Readonly<{
datastreamsILM: string;
beatsAgentComparison: string;
guide: string;
fleetServer: string;
fleetServerAddFleetServer: string;
settings: string;
settingsFleetServerHostSettings: string;
troubleshooting: string;
elasticAgent: string;
datastreams: string;
datastreamsNamingScheme: string;
installElasticAgent: string;
upgradeElasticAgent: string;
upgradeElasticAgent712lower: string;
learnMoreBlog: string;
apiKeysLearnMore: string;
}>;
readonly ecs: {
readonly guide: string;
};
readonly clients: {
readonly guide: string;
readonly goOverview: string;
readonly javaIndex: string;
readonly jsIntro: string;
readonly netGuide: string;
readonly perlGuide: string;
readonly phpGuide: string;
readonly pythonGuide: string;
readonly rubyOverview: string;
readonly rustGuide: string;
};
} | | diff --git a/packages/elastic-apm-synthtrace/README.md b/packages/elastic-apm-synthtrace/README.md index 3d65120b4b6c..cdbd53683167 100644 --- a/packages/elastic-apm-synthtrace/README.md +++ b/packages/elastic-apm-synthtrace/README.md @@ -93,10 +93,10 @@ const esEvents = toElasticsearchOutput([ Via the CLI, you can upload scenarios, either using a fixed time range or continuously generating data. Some examples are available in in `src/scripts/examples`. Here's an example for live data: -`$ node packages/elastic-apm-synthtrace/src/scripts/run packages/elastic-apm-generator/src/examples/01_simple_trace.ts --target=http://admin:changeme@localhost:9200 --live` +`$ node packages/elastic-apm-synthtrace/src/scripts/run packages/elastic-apm-synthtrace/src/scripts/examples/01_simple_trace.ts --target=http://admin:changeme@localhost:9200 --live` For a fixed time window: -`$ node packages/elastic-apm-synthtrace/src/scripts/run packages/elastic-apm-generator/src/examples/01_simple_trace.ts --target=http://admin:changeme@localhost:9200 --from=now-24h --to=now` +`$ node packages/elastic-apm-synthtrace/src/scripts/run packages/elastic-apm-synthtrace/src/scripts/examples/01_simple_trace.ts --target=http://admin:changeme@localhost:9200 --from=now-24h --to=now` The script will try to automatically find bootstrapped APM indices. __If these indices do not exist, the script will exit with an error. It will not bootstrap the indices itself.__ diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 2bbb4703ecd1..0cab7a72adae 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -479,6 +479,7 @@ export class DocLinksService { settingsFleetServerHostSettings: `${FLEET_DOCS}fleet-settings.html#fleet-server-hosts-setting`, troubleshooting: `${FLEET_DOCS}fleet-troubleshooting.html`, elasticAgent: `${FLEET_DOCS}elastic-agent-installation.html`, + beatsAgentComparison: `${FLEET_DOCS}beats-agent-comparison.html`, datastreams: `${FLEET_DOCS}data-streams.html`, datastreamsILM: `${FLEET_DOCS}data-streams.html#data-streams-ilm`, datastreamsNamingScheme: `${FLEET_DOCS}data-streams.html#data-streams-naming-scheme`, @@ -736,6 +737,7 @@ export interface DocLinksStart { readonly ingest: Record; readonly fleet: Readonly<{ datastreamsILM: string; + beatsAgentComparison: string; guide: string; fleetServer: string; fleetServerAddFleetServer: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index cf0b526aa9fd..5d63d2b6f77c 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -697,6 +697,7 @@ export interface DocLinksStart { readonly ingest: Record; readonly fleet: Readonly<{ datastreamsILM: string; + beatsAgentComparison: string; guide: string; fleetServer: string; fleetServerAddFleetServer: string; diff --git a/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx b/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx index d20bbdc1bf19..1f09ff06b4b2 100644 --- a/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx +++ b/src/plugins/expression_reveal_image/public/components/reveal_image_component.tsx @@ -118,11 +118,11 @@ function RevealImageComponent({ return imgStyles; } - const additionaAlignerStyles: AlignerStyles = {}; + const additionalAlignerStyles: AlignerStyles = {}; if (isValidUrl(emptyImage ?? '')) { // only use empty image if one is provided - additionaAlignerStyles.backgroundImage = `url(${emptyImage})`; + additionalAlignerStyles.backgroundImage = `url(${emptyImage})`; } let additionalImgStyles: ImageStyles = {}; @@ -136,10 +136,10 @@ function RevealImageComponent({ return (
{ + describe('dynamic suggestions for argument values', () => { describe('.es()', () => { it('should show index pattern suggestions for index argument', async () => { await monacoEditor.setCodeEditorValue(''); diff --git a/test/functional/page_objects/timelion_page.ts b/test/functional/page_objects/timelion_page.ts index bdfde3c8145e..ba1db60bc635 100644 --- a/test/functional/page_objects/timelion_page.ts +++ b/test/functional/page_objects/timelion_page.ts @@ -7,13 +7,21 @@ */ import { FtrService } from '../ftr_provider_context'; +import type { WebElementWrapper } from '../services/lib/web_element_wrapper'; export class TimelionPageObject extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); + private readonly retry = this.ctx.getService('retry'); public async getSuggestionItemsText() { - const timelionCodeEditor = await this.testSubjects.find('timelionCodeEditor'); - const lists = await timelionCodeEditor.findAllByClassName('monaco-list-row'); + let lists: WebElementWrapper[] = []; + await this.retry.try(async () => { + const timelionCodeEditor = await this.testSubjects.find('timelionCodeEditor'); + lists = await timelionCodeEditor.findAllByClassName('monaco-list-row'); + if (lists.length === 0) { + throw new Error('suggestion list not populated'); + } + }); return await Promise.all(lists.map(async (element) => await element.getVisibleText())); } diff --git a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.test.ts b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.test.ts new file mode 100644 index 000000000000..3940fa60a38f --- /dev/null +++ b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { preprocessLegacyFields } from './get_apm_package_policy_definition'; + +const apmServerSchema = { + 'apm-server.host': '0.0.0.0:8200', + 'apm-server.secret_token': 'asdfkjhasdf', + 'apm-server.read_timeout': 3600, + 'apm-server.rum.event_rate.limit': 100, + 'apm-server.rum.event_rate.lru_size': 100, + 'apm-server.rum.allow_service_names': 'opbeans-test', + 'logging.level': 'error', + 'queue.mem.events': 2000, + 'queue.mem.flush.timeout': '1s', + 'setup.template.settings.index.number_of_jshards': 1, +}; + +describe('get_apm_package_policy_definition', () => { + describe('preprocessLegacyFields', () => { + it('should replace legacy fields with supported fields', () => { + const result = preprocessLegacyFields({ apmServerSchema }); + expect(result).toMatchInlineSnapshot(` + Object { + "apm-server.auth.anonymous.allow_service": "opbeans-test", + "apm-server.auth.anonymous.rate_limit.event_limit": 100, + "apm-server.auth.anonymous.rate_limit.ip_limit": 100, + "apm-server.auth.secret_token": "asdfkjhasdf", + "apm-server.host": "0.0.0.0:8200", + "apm-server.read_timeout": 3600, + "logging.level": "error", + "queue.mem.events": 2000, + "queue.mem.flush.timeout": "1s", + "setup.template.settings.index.number_of_jshards": 1, + } + `); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts index 98b6a6489c47..df922dd18fe4 100644 --- a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts +++ b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts @@ -45,7 +45,7 @@ export function getApmPackagePolicyDefinition( }; } -function preprocessLegacyFields({ +export function preprocessLegacyFields({ apmServerSchema, }: { apmServerSchema: Record; @@ -64,6 +64,10 @@ function preprocessLegacyFields({ key: 'apm-server.auth.anonymous.allow_service', legacyKey: 'apm-server.rum.allow_service_names', }, + { + key: 'apm-server.auth.secret_token', + legacyKey: 'apm-server.secret_token', + }, ].forEach(({ key, legacyKey }) => { if (!copyOfApmServerSchema[key]) { copyOfApmServerSchema[key] = copyOfApmServerSchema[legacyKey]; diff --git a/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.test.ts b/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.test.ts new file mode 100644 index 000000000000..cdc56ba9794f --- /dev/null +++ b/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.test.ts @@ -0,0 +1,58 @@ +/* + * 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 { SavedObjectsClientContract } from 'kibana/server'; +import { getUnsupportedApmServerSchema } from './get_unsupported_apm_server_schema'; + +const apmServerSchema = { + 'apm-server.host': '0.0.0.0:8200', + 'apm-server.secret_token': 'asdfkjhasdf', + 'apm-server.read_timeout': 3600, + 'apm-server.rum.event_rate.limit': 100, + 'apm-server.rum.event_rate.lru_size': 100, + 'apm-server.rum.allow_service_names': 'opbeans-test', + 'logging.level': 'error', + 'queue.mem.events': 2000, + 'queue.mem.flush.timeout': '1s', + 'setup.template.settings.index.number_of_jshards': 1, +}; + +const mockSavaedObectsClient = { + get: () => ({ + attributes: { schemaJson: JSON.stringify(apmServerSchema) }, + }), +} as unknown as SavedObjectsClientContract; + +describe('get_unsupported_apm_server_schema', () => { + describe('getUnsupportedApmServerSchema', () => { + it('should return key-value pairs of unsupported configs', async () => { + const result = await getUnsupportedApmServerSchema({ + savedObjectsClient: mockSavaedObectsClient, + }); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "key": "logging.level", + "value": "error", + }, + Object { + "key": "queue.mem.events", + "value": 2000, + }, + Object { + "key": "queue.mem.flush.timeout", + "value": "1s", + }, + Object { + "key": "setup.template.settings.index.number_of_jshards", + "value": 1, + }, + ] + `); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.ts b/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.ts index 5fec3c94cf7a..2ced15245b59 100644 --- a/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.ts +++ b/x-pack/plugins/apm/server/lib/fleet/get_unsupported_apm_server_schema.ts @@ -10,7 +10,10 @@ import { APM_SERVER_SCHEMA_SAVED_OBJECT_TYPE, APM_SERVER_SCHEMA_SAVED_OBJECT_ID, } from '../../../common/apm_saved_object_constants'; -import { apmConfigMapping } from './get_apm_package_policy_definition'; +import { + apmConfigMapping, + preprocessLegacyFields, +} from './get_apm_package_policy_definition'; export async function getUnsupportedApmServerSchema({ savedObjectsClient, @@ -24,7 +27,10 @@ export async function getUnsupportedApmServerSchema({ const apmServerSchema: Record = JSON.parse( (attributes as { schemaJson: string }).schemaJson ); - return Object.entries(apmServerSchema) + const preprocessedApmServerSchema = preprocessLegacyFields({ + apmServerSchema, + }); + return Object.entries(preprocessedApmServerSchema) .filter(([name]) => !(name in apmConfigMapping)) .map(([key, value]) => ({ key, value })); } diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx index 1fed1d90689b..990d44584cf0 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx @@ -47,6 +47,7 @@ describe('ConfigureCases', () => { iconClass: 'logoSecurity', }); }); + beforeEach(() => { useActionTypesMock.mockImplementation(() => useActionTypesResponse); }); @@ -451,150 +452,149 @@ describe('ConfigureCases', () => { ).toBe('Update My Connector 2'); }); }); -}); -// Failing: See https://github.com/elastic/kibana/issues/115366 -describe.skip('closure options', () => { - let wrapper: ReactWrapper; - let persistCaseConfigure: jest.Mock; + describe('closure options', () => { + let wrapper: ReactWrapper; + let persistCaseConfigure: jest.Mock; - beforeEach(() => { - persistCaseConfigure = jest.fn(); - useCaseConfigureMock.mockImplementation(() => ({ - ...useCaseConfigureResponse, - mapping: null, - closureType: 'close-by-user', - connector: { - id: 'servicenow-1', - name: 'My connector', - type: ConnectorTypes.serviceNowITSM, - fields: null, - }, - currentConfiguration: { + beforeEach(() => { + persistCaseConfigure = jest.fn(); + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + mapping: null, + closureType: 'close-by-user', connector: { - id: 'My connector', + id: 'servicenow-1', name: 'My connector', - type: ConnectorTypes.jira, + type: ConnectorTypes.serviceNowITSM, fields: null, }, - closureType: 'close-by-user', - }, - persistCaseConfigure, - })); - useConnectorsMock.mockImplementation(() => useConnectorsResponse); - useGetUrlSearchMock.mockImplementation(() => searchURL); + currentConfiguration: { + connector: { + id: 'My connector', + name: 'My connector', + type: ConnectorTypes.jira, + fields: null, + }, + closureType: 'close-by-user', + }, + persistCaseConfigure, + })); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useGetUrlSearchMock.mockImplementation(() => searchURL); - wrapper = mount(, { - wrappingComponent: TestProviders, + wrapper = mount(, { + wrappingComponent: TestProviders, + }); }); - }); - test('it submits the configuration correctly when changing closure type', () => { - wrapper.find('input[id="close-by-pushing"]').simulate('change'); - wrapper.update(); + test('it submits the configuration correctly when changing closure type', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); - expect(persistCaseConfigure).toHaveBeenCalled(); - expect(persistCaseConfigure).toHaveBeenCalledWith({ - connector: { - id: 'servicenow-1', - name: 'My connector', - type: ConnectorTypes.serviceNowITSM, - fields: null, - }, - closureType: 'close-by-pushing', + expect(persistCaseConfigure).toHaveBeenCalled(); + expect(persistCaseConfigure).toHaveBeenCalledWith({ + connector: { + id: 'servicenow-1', + name: 'My connector', + type: ConnectorTypes.serviceNowITSM, + fields: null, + }, + closureType: 'close-by-pushing', + }); }); }); -}); -describe('user interactions', () => { - beforeEach(() => { - useCaseConfigureMock.mockImplementation(() => ({ - ...useCaseConfigureResponse, - mapping: null, - closureType: 'close-by-user', - connector: { - id: 'resilient-2', - name: 'unchanged', - type: ConnectorTypes.resilient, - fields: null, - }, - currentConfiguration: { + describe('user interactions', () => { + beforeEach(() => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + mapping: null, + closureType: 'close-by-user', connector: { id: 'resilient-2', name: 'unchanged', - type: ConnectorTypes.serviceNowITSM, + type: ConnectorTypes.resilient, fields: null, }, - closureType: 'close-by-user', - }, - })); - useConnectorsMock.mockImplementation(() => useConnectorsResponse); - useGetUrlSearchMock.mockImplementation(() => searchURL); - }); - - test('it show the add flyout when pressing the add connector button', async () => { - const wrapper = mount(, { - wrappingComponent: TestProviders, + currentConfiguration: { + connector: { + id: 'resilient-2', + name: 'unchanged', + type: ConnectorTypes.serviceNowITSM, + fields: null, + }, + closureType: 'close-by-user', + }, + })); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useGetUrlSearchMock.mockImplementation(() => searchURL); }); - wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); - wrapper.find('button[data-test-subj="dropdown-connector-add-connector"]').simulate('click'); + test('it show the add flyout when pressing the add connector button', async () => { + const wrapper = mount(, { + wrappingComponent: TestProviders, + }); - await waitFor(() => { - wrapper.update(); - expect(wrapper.find('ConnectorAddFlyout').exists()).toBe(true); - expect(wrapper.find('ConnectorAddFlyout').prop('actionTypes')).toEqual([ - expect.objectContaining({ - id: '.servicenow', - }), - expect.objectContaining({ - id: '.jira', - }), - expect.objectContaining({ - id: '.resilient', - }), - expect.objectContaining({ - id: '.servicenow-sir', - }), - ]); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find('button[data-test-subj="dropdown-connector-add-connector"]').simulate('click'); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('ConnectorAddFlyout').exists()).toBe(true); + expect(wrapper.find('ConnectorAddFlyout').prop('actionTypes')).toEqual([ + expect.objectContaining({ + id: '.servicenow', + }), + expect.objectContaining({ + id: '.jira', + }), + expect.objectContaining({ + id: '.resilient', + }), + expect.objectContaining({ + id: '.servicenow-sir', + }), + ]); + }); }); - }); - test('it show the edit flyout when pressing the update connector button', async () => { - const actionType = actionTypeRegistryMock.createMockActionTypeModel({ - id: '.resilient', - validateConnector: () => { - return Promise.resolve({}); - }, - validateParams: () => { - const validationResult = { errors: {} }; - return Promise.resolve(validationResult); - }, - actionConnectorFields: null, - }); - - useKibanaMock().services.triggersActionsUi.actionTypeRegistry.get = jest - .fn() - .mockReturnValue(actionType); - useKibanaMock().services.triggersActionsUi.actionTypeRegistry.has = jest - .fn() - .mockReturnValue(true); - - const wrapper = mount(, { - wrappingComponent: TestProviders, - }); - wrapper - .find('button[data-test-subj="case-configure-update-selected-connector-button"]') - .simulate('click'); - - await waitFor(() => { - wrapper.update(); - expect(wrapper.find('ConnectorEditFlyout').exists()).toBe(true); - expect(wrapper.find('ConnectorEditFlyout').prop('initialConnector')).toEqual(connectors[1]); - }); + test('it show the edit flyout when pressing the update connector button', async () => { + const actionType = actionTypeRegistryMock.createMockActionTypeModel({ + id: '.resilient', + validateConnector: () => { + return Promise.resolve({}); + }, + validateParams: () => { + const validationResult = { errors: {} }; + return Promise.resolve(validationResult); + }, + actionConnectorFields: null, + }); + + useKibanaMock().services.triggersActionsUi.actionTypeRegistry.get = jest + .fn() + .mockReturnValue(actionType); + useKibanaMock().services.triggersActionsUi.actionTypeRegistry.has = jest + .fn() + .mockReturnValue(true); + + const wrapper = mount(, { + wrappingComponent: TestProviders, + }); + wrapper + .find('button[data-test-subj="case-configure-update-selected-connector-button"]') + .simulate('click'); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('ConnectorEditFlyout').exists()).toBe(true); + expect(wrapper.find('ConnectorEditFlyout').prop('initialConnector')).toEqual(connectors[1]); + }); - expect( - wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() - ).toBeFalsy(); + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/integration_preference.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/integration_preference.tsx index 9c9027fb94ac..fa9b9a7ed190 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/integration_preference.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/integration_preference.tsx @@ -22,6 +22,8 @@ import { EuiFlexItem, } from '@elastic/eui'; +import { useStartServices } from '../../../hooks'; + export type IntegrationPreferenceType = 'recommended' | 'beats' | 'agent'; interface Option { @@ -34,23 +36,6 @@ export interface Props { onChange: (type: IntegrationPreferenceType) => void; } -const link = ( - - - -); - -const title = ( - -); - const recommendedTooltip = ( { const [idSelected, setIdSelected] = React.useState(initialType); + + const { docLinks } = useStartServices(); + + const link = ( + + + + ); + + const title = ( + + ); + const radios = options.map((option) => ({ id: option.type, value: option.type, diff --git a/x-pack/plugins/global_search_bar/public/components/__snapshots__/search_bar.test.tsx.snap b/x-pack/plugins/global_search_bar/public/components/__snapshots__/search_bar.test.tsx.snap deleted file mode 100644 index 8433d98c232d..000000000000 --- a/x-pack/plugins/global_search_bar/public/components/__snapshots__/search_bar.test.tsx.snap +++ /dev/null @@ -1,44 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`SearchBar correctly filters and sorts results 1`] = ` -Array [ - "Canvas • Kibana", - "Discover • Kibana", - "Graph • Kibana", -] -`; - -exports[`SearchBar correctly filters and sorts results 2`] = ` -Array [ - "Discover • Kibana", - "My Dashboard • Test", -] -`; - -exports[`SearchBar only display results from the last search 1`] = ` -Array [ - "Visualize • Kibana", - "Map • Kibana", -] -`; - -exports[`SearchBar only display results from the last search 2`] = ` -Array [ - "Visualize • Kibana", - "Map • Kibana", -] -`; - -exports[`SearchBar supports keyboard shortcuts 1`] = ` - -`; diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx index c8bd54540e6a..dd7b1f266694 100644 --- a/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.test.tsx @@ -6,15 +6,21 @@ */ import React from 'react'; -import { waitFor, act } from '@testing-library/react'; -import { ReactWrapper } from 'enzyme'; +import { act, render, screen, fireEvent } from '@testing-library/react'; import { of, BehaviorSubject } from 'rxjs'; import { filter, map } from 'rxjs/operators'; -import { mountWithIntl } from '@kbn/test/jest'; import { applicationServiceMock } from '../../../../../src/core/public/mocks'; import { globalSearchPluginMock } from '../../../global_search/public/mocks'; import { GlobalSearchBatchedResults, GlobalSearchResult } from '../../../global_search/public'; import { SearchBar } from './search_bar'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; + +jest.mock( + 'react-virtualized-auto-sizer', + () => + ({ children }: any) => + children({ height: 600, width: 600 }) +); type Result = { id: string; type: string } | string; @@ -36,9 +42,7 @@ const createResult = (result: Result): GlobalSearchResult => { const createBatch = (...results: Result[]): GlobalSearchBatchedResults => ({ results: results.map(createResult), }); - -const getSelectableProps: any = (component: any) => component.find('EuiSelectable').props(); -const getSearchProps: any = (component: any) => component.find('EuiFieldSearch').props(); +jest.useFakeTimers(); describe('SearchBar', () => { let searchService: ReturnType; @@ -46,31 +50,37 @@ describe('SearchBar', () => { const basePathUrl = '/plugins/globalSearchBar/assets/'; const darkMode = false; - let component: ReactWrapper; - beforeEach(() => { applications = applicationServiceMock.createStartContract(); searchService = globalSearchPluginMock.createStartContract(); - jest.useFakeTimers(); }); - const triggerFocus = () => { - component.find('input[data-test-subj="nav-search-input"]').simulate('focus'); - }; - const update = () => { act(() => { jest.runAllTimers(); }); - component.update(); }; - const simulateTypeChar = async (text: string) => { - await waitFor(() => getSearchProps(component).onInput({ currentTarget: { value: text } })); + const focusAndUpdate = async () => { + await act(async () => { + (await screen.findByTestId('nav-search-input')).focus(); + jest.runAllTimers(); + }); }; - const getDisplayedOptionsTitle = () => { - return getSelectableProps(component).options.map((option: any) => option.title); + const simulateTypeChar = (text: string) => { + fireEvent.input(screen.getByTestId('nav-search-input'), { target: { value: text } }); + act(() => { + jest.runAllTimers(); + }); + }; + + const assertSearchResults = async (list: string[]) => { + for (let i = 0; i < list.length; i++) { + expect(await screen.findByTitle(list[i])).toBeInTheDocument(); + } + + expect(await screen.findAllByTestId('nav-search-option')).toHaveLength(list.length); }; it('correctly filters and sorts results', async () => { @@ -83,53 +93,52 @@ describe('SearchBar', () => { ) .mockReturnValueOnce(of(createBatch('Discover', { id: 'My Dashboard', type: 'test' }))); - component = mountWithIntl( - + render( + + + ); expect(searchService.find).toHaveBeenCalledTimes(0); - triggerFocus(); - update(); + await focusAndUpdate(); expect(searchService.find).toHaveBeenCalledTimes(1); expect(searchService.find).toHaveBeenCalledWith({}, {}); - expect(getDisplayedOptionsTitle()).toMatchSnapshot(); + await assertSearchResults(['Canvas • Kibana', 'Discover • Kibana', 'Graph • Kibana']); - await simulateTypeChar('d'); - update(); + simulateTypeChar('d'); - expect(getDisplayedOptionsTitle()).toMatchSnapshot(); + await assertSearchResults(['Discover • Kibana', 'My Dashboard • Test']); expect(searchService.find).toHaveBeenCalledTimes(2); - expect(searchService.find).toHaveBeenCalledWith({ term: 'd' }, {}); + expect(searchService.find).toHaveBeenLastCalledWith({ term: 'd' }, {}); }); - it('supports keyboard shortcuts', () => { - mountWithIntl( - , - { attachTo: document.body } + it('supports keyboard shortcuts', async () => { + render( + + + ); + act(() => { + fireEvent.keyDown(window, { key: '/', ctrlKey: true, metaKey: true }); + }); - const searchEvent = new KeyboardEvent('keydown', { - key: '/', - ctrlKey: true, - metaKey: true, - } as any); - window.dispatchEvent(searchEvent); + const inputElement = await screen.findByTestId('nav-search-input'); - expect(document.activeElement).toMatchSnapshot(); + expect(document.activeElement).toEqual(inputElement); }); it('only display results from the last search', async () => { @@ -144,30 +153,29 @@ describe('SearchBar', () => { searchService.find.mockReturnValueOnce(firstSearch).mockReturnValueOnce(secondSearch); - component = mountWithIntl( - + render( + + + ); - triggerFocus(); - update(); + await focusAndUpdate(); expect(searchService.find).toHaveBeenCalledTimes(1); - - await simulateTypeChar('d'); - update(); - - expect(getDisplayedOptionsTitle()).toMatchSnapshot(); + // + simulateTypeChar('d'); + await assertSearchResults(['Visualize • Kibana', 'Map • Kibana']); firstSearchTrigger.next(true); update(); - expect(getDisplayedOptionsTitle()).toMatchSnapshot(); + await assertSearchResults(['Visualize • Kibana', 'Map • Kibana']); }); }); diff --git a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx index c459b2c04568..97e19bab3d2d 100644 --- a/x-pack/plugins/global_search_bar/public/components/search_bar.tsx +++ b/x-pack/plugins/global_search_bar/public/components/search_bar.tsx @@ -180,6 +180,7 @@ export function SearchBar({ darkMode, }: Props) { const isMounted = useMountedState(); + const [initialLoad, setInitialLoad] = useState(false); const [searchValue, setSearchValue] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [searchRef, setSearchRef] = useState(null); @@ -190,12 +191,14 @@ export function SearchBar({ const UNKNOWN_TAG_ID = '__unknown__'; useEffect(() => { - const fetch = async () => { - const types = await globalSearch.getSearchableTypes(); - setSearchableTypes(types); - }; - fetch(); - }, [globalSearch]); + if (initialLoad) { + const fetch = async () => { + const types = await globalSearch.getSearchableTypes(); + setSearchableTypes(types); + }; + fetch(); + } + }, [globalSearch, initialLoad]); const loadSuggestions = useCallback( (term: string) => { @@ -234,75 +237,80 @@ export function SearchBar({ useDebounce( () => { - // cancel pending search if not completed yet - if (searchSubscription.current) { - searchSubscription.current.unsubscribe(); - searchSubscription.current = null; - } + if (initialLoad) { + // cancel pending search if not completed yet + if (searchSubscription.current) { + searchSubscription.current.unsubscribe(); + searchSubscription.current = null; + } + + const suggestions = loadSuggestions(searchValue); + + let aggregatedResults: GlobalSearchResult[] = []; + if (searchValue.length !== 0) { + trackUiMetric(METRIC_TYPE.COUNT, 'search_request'); + } + + const rawParams = parseSearchParams(searchValue); + const tagIds = + taggingApi && rawParams.filters.tags + ? rawParams.filters.tags.map( + (tagName) => taggingApi.ui.getTagIdFromName(tagName) ?? UNKNOWN_TAG_ID + ) + : undefined; + const searchParams: GlobalSearchFindParams = { + term: rawParams.term, + types: rawParams.filters.types, + tags: tagIds, + }; + // TODO technically a subtle bug here + // this term won't be set until the next time the debounce is fired + // so the SearchOption won't highlight anything if only one call is fired + // in practice, this is hard to spot, unlikely to happen, and is a negligible issue + setSearchTerm(rawParams.term ?? ''); + + searchSubscription.current = globalSearch.find(searchParams, {}).subscribe({ + next: ({ results }) => { + if (searchValue.length > 0) { + aggregatedResults = [...results, ...aggregatedResults].sort(sortByScore); + setOptions(aggregatedResults, suggestions, searchParams.tags); + return; + } + + // if searchbar is empty, filter to only applications and sort alphabetically + results = results.filter(({ type }: GlobalSearchResult) => type === 'application'); + + aggregatedResults = [...results, ...aggregatedResults].sort(sortByTitle); - const suggestions = loadSuggestions(searchValue); - - let aggregatedResults: GlobalSearchResult[] = []; - if (searchValue.length !== 0) { - trackUiMetric(METRIC_TYPE.COUNT, 'search_request'); - } - - const rawParams = parseSearchParams(searchValue); - const tagIds = - taggingApi && rawParams.filters.tags - ? rawParams.filters.tags.map( - (tagName) => taggingApi.ui.getTagIdFromName(tagName) ?? UNKNOWN_TAG_ID - ) - : undefined; - const searchParams: GlobalSearchFindParams = { - term: rawParams.term, - types: rawParams.filters.types, - tags: tagIds, - }; - // TODO technically a subtle bug here - // this term won't be set until the next time the debounce is fired - // so the SearchOption won't highlight anything if only one call is fired - // in practice, this is hard to spot, unlikely to happen, and is a negligible issue - setSearchTerm(rawParams.term ?? ''); - - searchSubscription.current = globalSearch.find(searchParams, {}).subscribe({ - next: ({ results }) => { - if (searchValue.length > 0) { - aggregatedResults = [...results, ...aggregatedResults].sort(sortByScore); setOptions(aggregatedResults, suggestions, searchParams.tags); - return; - } - - // if searchbar is empty, filter to only applications and sort alphabetically - results = results.filter(({ type }: GlobalSearchResult) => type === 'application'); - - aggregatedResults = [...results, ...aggregatedResults].sort(sortByTitle); - - setOptions(aggregatedResults, suggestions, searchParams.tags); - }, - error: () => { - // Not doing anything on error right now because it'll either just show the previous - // results or empty results which is basically what we want anyways - trackUiMetric(METRIC_TYPE.COUNT, 'unhandled_error'); - }, - complete: () => {}, - }); + }, + error: () => { + // Not doing anything on error right now because it'll either just show the previous + // results or empty results which is basically what we want anyways + trackUiMetric(METRIC_TYPE.COUNT, 'unhandled_error'); + }, + complete: () => {}, + }); + } }, 350, - [searchValue, loadSuggestions] + [searchValue, loadSuggestions, searchableTypes, initialLoad] ); - const onKeyDown = (event: KeyboardEvent) => { - if (event.key === '/' && (isMac ? event.metaKey : event.ctrlKey)) { - event.preventDefault(); - trackUiMetric(METRIC_TYPE.COUNT, 'shortcut_used'); - if (searchRef) { - searchRef.focus(); - } else if (buttonRef) { - (buttonRef.children[0] as HTMLButtonElement).click(); + const onKeyDown = useCallback( + (event: KeyboardEvent) => { + if (event.key === '/' && (isMac ? event.metaKey : event.ctrlKey)) { + event.preventDefault(); + trackUiMetric(METRIC_TYPE.COUNT, 'shortcut_used'); + if (searchRef) { + searchRef.focus(); + } else if (buttonRef) { + (buttonRef.children[0] as HTMLButtonElement).click(); + } } - } - }; + }, + [buttonRef, searchRef, trackUiMetric] + ); const onChange = (selection: EuiSelectableTemplateSitewideOption[]) => { const selected = selection.find(({ checked }) => checked === 'on'); @@ -411,6 +419,7 @@ export function SearchBar({ }), onFocus: () => { trackUiMetric(METRIC_TYPE.COUNT, 'search_focus'); + setInitialLoad(true); }, }} popoverProps={{ diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts index be4f99103b31..4c553cac0230 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts @@ -12,7 +12,8 @@ import { PhaseWithTiming } from '../../../../common/types'; import { setupEnvironment } from '../../helpers'; import { setupValidationTestBed, ValidationTestBed } from './validation.helpers'; -describe(' timing validation', () => { +// FLAKY: https://github.com/elastic/kibana/issues/115307 +describe.skip(' timing validation', () => { let testBed: ValidationTestBed; let actions: ValidationTestBed['actions']; const { server, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx index d6d030c3ec73..61ce87860d89 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/policy_list/components/policy_table.tsx @@ -191,7 +191,9 @@ export const PolicyTable: React.FunctionComponent = ({ policies }) => { direction: 'asc', }, }} - search={{ box: { incremental: true } }} + search={{ + box: { incremental: true, 'data-test-subj': 'ilmSearchBar' }, + }} tableLayout="auto" items={policies} columns={columns} diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js index 1854982c8db0..7f9fcc7bc551 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasts_list.js @@ -78,6 +78,12 @@ function getColumns(viewForecast) { // TODO - add in ml-info-icon to the h3 element, // then remove tooltip and inline style. export function ForecastsList({ forecasts, viewForecast }) { + const getRowProps = (item) => { + return { + 'data-test-subj': `mlForecastsListRow row-${item.rowId}`, + }; + }; + return (

); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index 87131583e44e..cad5bb68fb62 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -547,9 +547,18 @@ class TimeseriesChartIntl extends Component { // Create the path elements for the forecast value line and bounds area. if (contextForecastData) { - fcsGroup.append('path').attr('class', 'area forecast'); - fcsGroup.append('path').attr('class', 'values-line forecast'); - fcsGroup.append('g').attr('class', 'focus-chart-markers forecast'); + fcsGroup + .append('path') + .attr('class', 'area forecast') + .attr('data-test-subj', 'mlForecastArea'); + fcsGroup + .append('path') + .attr('class', 'values-line forecast') + .attr('data-test-subj', 'mlForecastValuesline'); + fcsGroup + .append('g') + .attr('class', 'focus-chart-markers forecast') + .attr('data-test-subj', 'mlForecastMarkers'); } fcsGroup diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 9b8770350909..e4d7fc457de0 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -1170,9 +1170,13 @@ export class TimeSeriesExplorer extends React.Component { + {i18n.translate('xpack.ml.timeSeriesExplorer.showForecastLabel', { + defaultMessage: 'show forecast', + })} + + } checked={showForecast} onChange={this.toggleShowForecastHandler} /> diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx index 496e7a10f9c4..eca18f0eb0dd 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/report_metric_options.tsx @@ -13,6 +13,8 @@ import { EuiListGroup, EuiListGroupItem, EuiBadge, + EuiText, + EuiLoadingSpinner, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -33,7 +35,7 @@ export function ReportMetricOptions({ seriesId, series, seriesConfig }: Props) { const [showOptions, setShowOptions] = useState(false); const metricOptions = seriesConfig?.metricOptions; - const { indexPatterns } = useAppIndexPatternContext(); + const { indexPatterns, loading } = useAppIndexPatternContext(); const onChange = (value?: string) => { setSeries(seriesId, { @@ -78,6 +80,10 @@ export function ReportMetricOptions({ seriesId, series, seriesConfig }: Props) { }; }); + if (!indexPattern && !loading) { + return {NO_DATA_AVAILABLE}; + } + return ( <> {!series.selectedMetricField && ( @@ -88,6 +94,7 @@ export function ReportMetricOptions({ seriesId, series, seriesConfig }: Props) { onClick={() => setShowOptions((prevState) => !prevState)} fill size="s" + isLoading={!indexPattern && loading} > {SELECT_REPORT_METRIC_LABEL} @@ -107,19 +114,23 @@ export function ReportMetricOptions({ seriesId, series, seriesConfig }: Props) { )} - {series.selectedMetricField && ( - onChange(undefined)} - iconOnClickAriaLabel={REMOVE_REPORT_METRIC_LABEL} - > - { - seriesConfig?.metricOptions?.find((option) => option.id === series.selectedMetricField) - ?.label - } - - )} + {series.selectedMetricField && + (indexPattern && !loading ? ( + onChange(undefined)} + iconOnClickAriaLabel={REMOVE_REPORT_METRIC_LABEL} + > + { + seriesConfig?.metricOptions?.find( + (option) => option.id === series.selectedMetricField + )?.label + } + + ) : ( + + ))} ); } @@ -137,3 +148,7 @@ const REMOVE_REPORT_METRIC_LABEL = i18n.translate( defaultMessage: 'Remove report metric', } ); + +const NO_DATA_AVAILABLE = i18n.translate('xpack.observability.expView.seriesEditor.noData', { + defaultMessage: 'No data available', +}); diff --git a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts index 7677f37702f0..7b4cc2008a67 100644 --- a/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts +++ b/x-pack/plugins/reporting/server/routes/diagnostic/browser.test.ts @@ -28,7 +28,8 @@ type SetupServerReturn = UnwrapPromise>; const devtoolMessage = 'DevTools listening on (ws://localhost:4000)'; const fontNotFoundMessage = 'Could not find the default font'; -describe('POST /diagnose/browser', () => { +// FLAKY: https://github.com/elastic/kibana/issues/89369 +describe.skip('POST /diagnose/browser', () => { jest.setTimeout(6000); const reportingSymbol = Symbol('reporting'); const mockLogger = createMockLevelLogger(); diff --git a/x-pack/plugins/reporting/server/routes/lib/request_handler.ts b/x-pack/plugins/reporting/server/routes/lib/request_handler.ts index a87f5c291303..2100c4c3c43a 100644 --- a/x-pack/plugins/reporting/server/routes/lib/request_handler.ts +++ b/x-pack/plugins/reporting/server/routes/lib/request_handler.ts @@ -6,6 +6,7 @@ */ import Boom from '@hapi/boom'; +import { i18n } from '@kbn/i18n'; import { KibanaRequest, KibanaResponseFactory } from 'kibana/server'; import { ReportingCore } from '../..'; import { API_BASE_URL } from '../../../common/constants'; @@ -153,7 +154,13 @@ export class RequestHandler { }); } - // unknown error, can't convert to 4xx - throw err; + return this.res.customError({ + statusCode: 500, + body: + err?.message || + i18n.translate('xpack.reporting.errorHandler.unknownError', { + defaultMessage: 'Unknown error', + }), + }); } } diff --git a/x-pack/plugins/reporting/server/routes/management/jobs.test.ts b/x-pack/plugins/reporting/server/routes/management/jobs.test.ts index 02a0ddc94a04..a54be44258ed 100644 --- a/x-pack/plugins/reporting/server/routes/management/jobs.test.ts +++ b/x-pack/plugins/reporting/server/routes/management/jobs.test.ts @@ -178,6 +178,36 @@ describe('GET /api/reporting/jobs/download', () => { await supertest(httpSetup.server.listener).get('/api/reporting/jobs/download/poo').expect(401); }); + it(`returns job's info`, async () => { + mockEsClient.search.mockResolvedValueOnce({ + body: getHits({ + jobtype: 'base64EncodedJobType', + payload: {}, // payload is irrelevant + }), + } as any); + + registerJobInfoRoutes(core); + + await server.start(); + + await supertest(httpSetup.server.listener).get('/api/reporting/jobs/info/test').expect(200); + }); + + it(`returns 403 if a user cannot view a job's info`, async () => { + mockEsClient.search.mockResolvedValueOnce({ + body: getHits({ + jobtype: 'customForbiddenJobType', + payload: {}, // payload is irrelevant + }), + } as any); + + registerJobInfoRoutes(core); + + await server.start(); + + await supertest(httpSetup.server.listener).get('/api/reporting/jobs/info/test').expect(403); + }); + it('when a job is incomplete', async () => { mockEsClient.search.mockResolvedValueOnce({ body: getHits({ diff --git a/x-pack/plugins/reporting/server/routes/management/jobs.ts b/x-pack/plugins/reporting/server/routes/management/jobs.ts index 99c317453ca0..54fc13ffbb61 100644 --- a/x-pack/plugins/reporting/server/routes/management/jobs.ts +++ b/x-pack/plugins/reporting/server/routes/management/jobs.ts @@ -5,8 +5,8 @@ * 2.0. */ -import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; import { ReportingCore } from '../../'; import { ROUTE_TAG_CAN_REDIRECT } from '../../../../security/server'; import { API_BASE_URL } from '../../../common/constants'; @@ -115,7 +115,12 @@ export function registerJobInfoRoutes(reporting: ReportingCore) { const { jobtype: jobType } = result; if (!jobTypes.includes(jobType)) { - throw Boom.unauthorized(`Sorry, you are not authorized to view ${jobType} info`); + return res.forbidden({ + body: i18n.translate('xpack.reporting.jobsQuery.infoError.unauthorizedErrorMessage', { + defaultMessage: 'Sorry, you are not authorized to view {jobType} info', + values: { jobType }, + }), + }); } return res.ok({ diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts index 1c59e56c0466..bd95e7e94252 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts @@ -50,6 +50,11 @@ export const technicalRuleFieldMap = { array: false, required: false, }, + [Fields.ALERT_RISK_SCORE]: { + type: 'float', + array: false, + required: false, + }, [Fields.ALERT_WORKFLOW_STATUS]: { type: 'keyword', array: false, diff --git a/x-pack/plugins/rule_registry/common/field_map/types.ts b/x-pack/plugins/rule_registry/common/field_map/types.ts index 3ff68315e93a..d4bdd34656ec 100644 --- a/x-pack/plugins/rule_registry/common/field_map/types.ts +++ b/x-pack/plugins/rule_registry/common/field_map/types.ts @@ -6,5 +6,5 @@ */ export interface FieldMap { - [key: string]: { type: string; required?: boolean; array?: boolean }; + [key: string]: { type: string; required?: boolean; array?: boolean; path?: string }; } diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts index 041dfdeed42e..3798506eeacd 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/resource_installer.ts @@ -315,6 +315,7 @@ export class ResourceInstaller { // @ts-expect-error rollover_alias: primaryNamespacedAlias, }, + 'index.mapping.total_fields.limit': 1100, }, mappings: { dynamic: false, diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts index 7dea0f917247..e575b49d1776 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts @@ -30,7 +30,7 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper .getWriter({ namespace: options.spaceId }) .bulk({ body: alerts.flatMap((alert) => [ - { index: {} }, + { index: { _id: alert.id } }, { [VERSION]: ruleDataClient.kibanaVersion, ...commonRuleFields, diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts index 2cde29ec9da6..803ff4b4d0d8 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts @@ -46,9 +46,10 @@ describe('Alert details with unmapped fields', () => { }); }); + // This test needs to be updated to not look for the field in a specific row, as it prevents us from adding/removing fields it('Displays the unmapped field on the table', () => { const expectedUnmmappedField = { - row: 54, + row: 82, field: 'unmapped', text: 'This is the unmapped field', }; diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts index 84ad93fa0894..cea290eeef17 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_alert.spec.ts @@ -35,7 +35,7 @@ import { import { ALERTS_URL } from '../../urls/navigation'; import { cleanKibana } from '../../tasks/common'; -describe('From alert', () => { +describe.skip('From alert', () => { const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert'; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts index ea6b6cf0186b..4af6467e5d33 100644 --- a/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/exceptions/from_rule.spec.ts @@ -35,7 +35,7 @@ import { refreshPage } from '../../tasks/security_header'; import { ALERTS_URL } from '../../urls/navigation'; import { cleanKibana } from '../../tasks/common'; -describe('From rule', () => { +describe.skip('From rule', () => { const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1'; beforeEach(() => { cleanKibana(); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx index 0a912598c572..0717ca5193be 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useEffect, useState, useMemo } from 'react'; -import { EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiCallOut, EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useDispatch } from 'react-redux'; @@ -23,7 +23,11 @@ import { getPolicyDetailPath, getPolicyTrustedAppsPath } from '../../../../commo import { PolicyDetailsForm } from '../policy_details_form'; import { AppAction } from '../../../../../common/store/actions'; import { usePolicyDetailsSelector } from '../policy_hooks'; -import { policyDetailsForUpdate } from '../../store/policy_details/selectors'; +import { + apiError, + policyDetails, + policyDetailsForUpdate, +} from '../../store/policy_details/selectors'; import { FleetTrustedAppsCard } from './endpoint_package_custom_extension/components/fleet_trusted_apps_card'; import { LinkWithIcon } from './endpoint_package_custom_extension/components/link_with_icon'; /** @@ -48,6 +52,8 @@ const WrappedPolicyDetailsForm = memo<{ }>(({ policyId, onChange }) => { const dispatch = useDispatch<(a: AppAction) => void>(); const updatedPolicy = usePolicyDetailsSelector(policyDetailsForUpdate); + const endpointPolicyDetails = usePolicyDetailsSelector(policyDetails); + const endpointDetailsLoadingError = usePolicyDetailsSelector(apiError); const { getAppUrl } = useAppUrl(); const [, setLastUpdatedPolicy] = useState(updatedPolicy); // TODO: Remove this and related code when removing FF @@ -185,7 +191,25 @@ const WrappedPolicyDetailsForm = memo<{

- + {endpointDetailsLoadingError ? ( + + } + iconType="alert" + color="warning" + data-test-subj="endpiontPolicySettingsLoadingError" + > + {endpointDetailsLoadingError.message} + + ) : !endpointPolicyDetails ? ( + + ) : ( + + )}
) : ( diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index 4ef3291e1b8f..b5d4c6033e98 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -41,7 +41,7 @@ import { findAllUnenrolledAgentIds } from './support/unenroll'; import { getAllEndpointPackagePolicies } from './support/endpoint_package_policies'; import { findAgentIdsByStatus } from './support/agent_status'; import { EndpointAppContextService } from '../../endpoint_app_context_services'; -import { fleetAgentStatusToEndpointHostStatus } from '../../utils'; +import { catchAndWrapError, fleetAgentStatusToEndpointHostStatus } from '../../utils'; import { queryResponseToHostListResult, queryResponseToHostResult, @@ -194,7 +194,9 @@ export async function getHostMetaData( const query = getESQueryHostMetadataByID(id); - const response = await esClient.asCurrentUser.search(query); + const response = await esClient.asCurrentUser + .search(query) + .catch(catchAndWrapError); const hostResult = queryResponseToHostResult(response.body); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts index 5f70a5ec20bf..1b6d85540f91 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/create_security_rule_type_wrapper.ts @@ -37,6 +37,7 @@ import { bulkCreateFactory, wrapHitsFactory, wrapSequencesFactory } from './fact import { RuleExecutionLogClient, truncateMessageList } from '../rule_execution_log'; import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas'; import { scheduleThrottledNotificationActions } from '../notifications/schedule_throttle_notification_actions'; +import aadFieldConversion from '../routes/index/signal_aad_mapping.json'; /* eslint-disable complexity */ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = @@ -225,8 +226,9 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = refresh ); + const legacySignalFields: string[] = Object.keys(aadFieldConversion); const wrapHits = wrapHitsFactory({ - ignoreFields, + ignoreFields: [...ignoreFields, ...legacySignalFields], mergeStrategy, completeRule, spaceId, @@ -234,7 +236,7 @@ export const createSecurityRuleTypeWrapper: CreateSecurityRuleTypeWrapper = const wrapSequences = wrapSequencesFactory({ logger, - ignoreFields, + ignoreFields: [...ignoreFields, ...legacySignalFields], mergeStrategy, completeRule, spaceId, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts index a66703e3a50b..81a4af31881f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/wrap_hits_factory.ts @@ -52,5 +52,5 @@ export const wrapHitsFactory = }; }); - return filterDuplicateSignals(completeRule.alertId, wrappedDocs, false); + return filterDuplicateSignals(completeRule.alertId, wrappedDocs, true); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts index 9cc5c63332a5..5a7ceb83baf8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/alerts.ts @@ -163,6 +163,11 @@ export const alertsFieldMap: FieldMap = { array: false, required: true, }, + 'kibana.alert.original_event.severity': { + type: 'long', + array: false, + required: false, + }, 'kibana.alert.original_event.start': { type: 'date', array: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts index 87b55e092ec5..a067a36fe039 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/field_maps/rules.ts @@ -126,17 +126,17 @@ export const rulesFieldMap = { array: true, required: false, }, - 'kibana.alert.rule.threat_mapping.field': { + 'kibana.alert.rule.threat_mapping.entries.field': { type: 'keyword', array: true, required: false, }, - 'kibana.alert.rule.threat_mapping.value': { + 'kibana.alert.rule.threat_mapping.entries.value': { type: 'keyword', array: true, required: false, }, - 'kibana.alert.rule.threat_mapping.type': { + 'kibana.alert.rule.threat_mapping.entries.type': { type: 'keyword', array: true, required: false, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts index f029b02127b0..ff4fbb58d749 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threat_mapping/utils.test.ts @@ -723,7 +723,7 @@ describe('utils', () => { it('throws an error if the validator is called after the specified interval', async () => { const validator = buildExecutionIntervalValidator('1s'); - await new Promise((r) => setTimeout(r, 1001)); + await new Promise((r) => setTimeout(r, 2000)); expect(() => validator()).toThrowError( 'Current rule execution has exceeded its allotted interval (1s) and has been stopped.' ); diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index b31ec3696fd4..307ccf4cfc97 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -7,7 +7,6 @@ import { Observable } from 'rxjs'; import LRU from 'lru-cache'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { SIGNALS_ID, QUERY_RULE_TYPE_ID, @@ -22,6 +21,8 @@ import { Logger, SavedObjectsClient } from '../../../../src/core/server'; import { UsageCounter } from '../../../../src/plugins/usage_collection/server'; import { ECS_COMPONENT_TEMPLATE_NAME } from '../../rule_registry/common/assets'; +import { FieldMap } from '../../rule_registry/common/field_map'; +import { technicalRuleFieldMap } from '../../rule_registry/common/assets/field_maps/technical_rule_field_map'; import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map'; import { IRuleDataClient, Dataset } from '../../rule_registry/server'; import { ListPluginSetup } from '../../lists/server'; @@ -188,13 +189,9 @@ export class Plugin implements ISecuritySolutionPlugin { }; if (isRuleRegistryEnabled) { - // NOTE: this is not used yet - // TODO: convert the aliases to FieldMaps. Requires enhancing FieldMap to support alias path. - // Split aliases by component template since we need to alias some fields in technical field mappings, - // some fields in security solution specific component template. - const aliases: Record = {}; + const aliasesFieldMap: FieldMap = {}; Object.entries(aadFieldConversion).forEach(([key, value]) => { - aliases[key] = { + aliasesFieldMap[key] = { type: 'alias', path: value, }; @@ -208,7 +205,10 @@ export class Plugin implements ISecuritySolutionPlugin { componentTemplates: [ { name: 'mappings', - mappings: mappingFromFieldMap({ ...alertsFieldMap, ...rulesFieldMap }, false), + mappings: mappingFromFieldMap( + { ...technicalRuleFieldMap, ...alertsFieldMap, ...rulesFieldMap, ...aliasesFieldMap }, + false + ), }, ], secondaryAlias: config.signalsIndex, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts index ae68d81d6b92..fbc51aa0360c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts @@ -22,9 +22,7 @@ import { HostValue, } from '../../../../../../common/search_strategy/security_solution/hosts'; import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array'; -import { getHostMetaData } from '../../../../../endpoint/routes/metadata/handlers'; import { EndpointAppContext } from '../../../../../endpoint/types'; -import { fleetAgentStatusToEndpointHostStatus } from '../../../../../endpoint/utils'; import { getPendingActionCounts } from '../../../../../endpoint/services'; export const HOST_FIELDS = [ @@ -184,51 +182,54 @@ export const getHostEndpoint = async ( endpointContext: EndpointAppContext; } ): Promise => { - const { esClient, endpointContext, savedObjectsClient } = deps; + if (!id) { + return null; + } + + const { esClient, endpointContext } = deps; const logger = endpointContext.logFactory.get('metadata'); + try { const agentService = endpointContext.service.getAgentService(); - if (agentService === undefined) { + + if (!agentService) { throw new Error('agentService not available'); } - const metadataRequestContext = { - esClient, - endpointAppContextService: endpointContext.service, - logger, - savedObjectsClient, - }; - const endpointData = - id != null && metadataRequestContext.endpointAppContextService.getAgentService() != null - ? await getHostMetaData(metadataRequestContext, id) - : null; - - const fleetAgentId = endpointData?.elastic.agent.id; - const [fleetAgentStatus, pendingActions] = !fleetAgentId - ? [undefined, {}] - : await Promise.all([ - // Get Agent Status - agentService.getAgentStatusById(esClient.asCurrentUser, fleetAgentId), - // Get a list of pending actions (if any) - getPendingActionCounts( - esClient.asCurrentUser, - endpointContext.service.getEndpointMetadataService(), - [fleetAgentId] - ).then((results) => { + + const endpointData = await endpointContext.service + .getEndpointMetadataService() + // Using `internalUser` ES client below due to the fact that Fleet data has been moved to + // system indices (`.fleet*`). Because this is a readonly action, this should be ok to do + // here until proper RBOC controls are implemented + .getEnrichedHostMetadata(esClient.asInternalUser, id); + + const fleetAgentId = endpointData.metadata.elastic.agent.id; + + const pendingActions = fleetAgentId + ? getPendingActionCounts( + esClient.asInternalUser, + endpointContext.service.getEndpointMetadataService(), + [fleetAgentId] + ) + .then((results) => { return results[0].pending_actions; - }), - ]); - - return endpointData != null && endpointData - ? { - endpointPolicy: endpointData.Endpoint.policy.applied.name, - policyStatus: endpointData.Endpoint.policy.applied.status, - sensorVersion: endpointData.agent.version, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - elasticAgentStatus: fleetAgentStatusToEndpointHostStatus(fleetAgentStatus!), - isolation: endpointData.Endpoint.state?.isolation ?? false, - pendingActions, - } - : null; + }) + .catch((error) => { + // Failure in retrieving the number of pending actions should not fail the entire + // call to get endpoint details. Log the error and return an empty object + logger.warn(error); + return {}; + }) + : {}; + + return { + endpointPolicy: endpointData.metadata.Endpoint.policy.applied.name, + policyStatus: endpointData.metadata.Endpoint.policy.applied.status, + sensorVersion: endpointData.metadata.agent.version, + elasticAgentStatus: endpointData.host_status, + isolation: endpointData.metadata.Endpoint.state?.isolation ?? false, + pendingActions, + }; } catch (err) { logger.warn(err); return null; diff --git a/x-pack/plugins/transform/common/api_schemas/transforms.ts b/x-pack/plugins/transform/common/api_schemas/transforms.ts index 8867ecb5cc76..55ea326069f0 100644 --- a/x-pack/plugins/transform/common/api_schemas/transforms.ts +++ b/x-pack/plugins/transform/common/api_schemas/transforms.ts @@ -94,6 +94,13 @@ function transformConfigPayloadValidator< } } +export const _metaSchema = schema.object( + {}, + { + unknowns: 'allow', + } +); + // PUT transforms/{transformId} export const putTransformsRequestSchema = schema.object( { @@ -112,6 +119,11 @@ export const putTransformsRequestSchema = schema.object( settings: schema.maybe(settingsSchema), source: sourceSchema, sync: schema.maybe(syncSchema), + /** + * This _meta field stores an arbitrary key-value map + * where keys are strings and values are arbitrary objects (possibly also maps). + */ + _meta: schema.maybe(_metaSchema), }, { validate: transformConfigPayloadValidator, diff --git a/x-pack/plugins/transform/common/types/transform.ts b/x-pack/plugins/transform/common/types/transform.ts index a478946ff917..92ffc0b99bc3 100644 --- a/x-pack/plugins/transform/common/types/transform.ts +++ b/x-pack/plugins/transform/common/types/transform.ts @@ -24,6 +24,7 @@ export type TransformBaseConfig = PutTransformsRequestSchema & { create_time?: number; version?: string; alerting_rules?: TransformHealthAlertRule[]; + _meta?: Record; }; export interface PivotConfigDefinition { diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts index 8f8341260bd7..184e3d31e89d 100644 --- a/x-pack/plugins/transform/public/app/common/request.ts +++ b/x-pack/plugins/transform/public/app/common/request.ts @@ -242,6 +242,7 @@ export const getCreateTransformRequestBody = ( }, } : {}), + ...(transformDetailsState._meta ? { _meta: transformDetailsState._meta } : {}), // conditionally add additional settings ...getCreateTransformSettingsRequestBody(transformDetailsState), }); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts index 39b1a2de26f8..21e6bce204ec 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/common.ts @@ -27,6 +27,7 @@ export interface StepDetailsExposedState { transformSettingsDocsPerSecond?: number; valid: boolean; indexPatternTimeField?: string | undefined; + _meta?: Record; } const defaultContinuousModeDelay = '60s'; @@ -94,6 +95,10 @@ export function applyTransformConfigToDetailsState( state.transformSettingsDocsPerSecond = transformConfig.settings.docs_per_second; } } + + if (transformConfig._meta) { + state._meta = transformConfig._meta; + } } return state; } diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 416bad15d800..eda95013f60b 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -289,6 +289,7 @@ export const StepDetailsForm: FC = React.memo( touched: true, valid, indexPatternTimeField, + _meta: defaults._meta, }); // custom comparison /* eslint-disable react-hooks/exhaustive-deps */ diff --git a/x-pack/test/accessibility/apps/index_lifecycle_management.ts b/x-pack/test/accessibility/apps/index_lifecycle_management.ts index 35f4a8e1adea..6cec8d1cb891 100644 --- a/x-pack/test/accessibility/apps/index_lifecycle_management.ts +++ b/x-pack/test/accessibility/apps/index_lifecycle_management.ts @@ -7,6 +7,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; +const REPO_NAME = 'test'; const POLICY_NAME = 'ilm-a11y-test'; const POLICY_ALL_PHASES = { policy: { @@ -23,7 +24,7 @@ const POLICY_ALL_PHASES = { frozen: { actions: { searchable_snapshot: { - snapshot_repository: 'test', + snapshot_repository: REPO_NAME, }, }, }, @@ -46,7 +47,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esClient = getService('es'); const a11y = getService('a11y'); + const filterByPolicyName = async (policyName: string) => { + await testSubjects.setValue('ilmSearchBar', policyName); + }; + const findPolicyLinkInListView = async (policyName: string) => { + await filterByPolicyName(policyName); const links = await testSubjects.findAll('policyTablePolicyNameLink'); for (const link of links) { const name = await link.getVisibleText(); @@ -57,11 +63,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { throw new Error(`Could not find ${policyName} in policy table`); }; - // FLAKY - // https://github.com/elastic/kibana/issues/114541 - // https://github.com/elastic/kibana/issues/114542 - describe.skip('Index Lifecycle Management', async () => { + describe('Index Lifecycle Management', async () => { before(async () => { + await esClient.snapshot.createRepository({ + name: REPO_NAME, + body: { + type: 'fs', + settings: { + // use one of the values defined in path.repo in test/functional/config.js + location: '/tmp/', + }, + }, + verify: false, + }); await esClient.ilm.putLifecycle({ name: POLICY_NAME, body: POLICY_ALL_PHASES }); await esClient.indices.putIndexTemplate({ name: indexTemplateName, @@ -79,6 +93,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async () => { + await esClient.snapshot.deleteRepository({ + name: REPO_NAME, + }); await esClient.ilm.deleteLifecycle({ name: POLICY_NAME }); await esClient.indices.deleteIndexTemplate({ name: indexTemplateName }); }); @@ -144,6 +161,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Add policy to index template modal', async () => { + await filterByPolicyName(POLICY_NAME); const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); const addPolicyButton = await policyRow.findByTestSubject('addPolicyToTemplate'); @@ -157,6 +175,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Delete policy modal', async () => { + await filterByPolicyName(POLICY_NAME); const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); const deleteButton = await policyRow.findByTestSubject('deletePolicy'); @@ -170,6 +189,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Index templates flyout', async () => { + await filterByPolicyName(POLICY_NAME); const policyRow = await testSubjects.find(`policyTableRow-${POLICY_NAME}`); const actionsButton = await policyRow.findByTestSubject('viewIndexTemplates'); diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 095c4f2cb59d..4781df7993a4 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -962,7 +962,9 @@ export const deleteRule = async ( if (response.status !== 200) { // eslint-disable-next-line no-console console.log( - 'Did not get an expected 200 "ok" when deleting the rule. CI issues could happen. Suspect this line if you are seeing CI issues.' + `Did not get an expected 200 "ok" when deleting the rule. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` ); } @@ -1111,7 +1113,7 @@ export const createExceptionList = async ( }; /** - * Helper to cut down on the noise in some of the tests. Does a delete of a rule. + * Helper to cut down on the noise in some of the tests. Does a delete of an exception list. * It does not check for a 200 "ok" on this. * @param supertest The supertest deps * @param id The rule id to delete @@ -1126,7 +1128,9 @@ export const deleteExceptionList = async ( if (response.status !== 200) { // eslint-disable-next-line no-console console.log( - 'Did not get an expected 200 "ok" when deleting an exception list. CI issues could happen. Suspect this line if you are seeing CI issues.' + `Did not get an expected 200 "ok" when deleting an exception list. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` ); } @@ -1143,12 +1147,20 @@ export const createExceptionListItem = async ( supertest: SuperTest.SuperTest, exceptionListItem: CreateExceptionListItemSchema ): Promise => { - const { body } = await supertest + const response = await supertest .post(EXCEPTION_LIST_ITEM_URL) .set('kbn-xsrf', 'true') - .send(exceptionListItem) - .expect(200); - return body; + .send(exceptionListItem); + + if (response.status !== 200) { + // eslint-disable-next-line no-console + console.log( + `Did not get an expected 200 "ok" when creating an exception list item. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return response.body; }; /** diff --git a/x-pack/test/examples/search_examples/search_sessions_cache.ts b/x-pack/test/examples/search_examples/search_sessions_cache.ts index 7e52849ed2a7..0da2de46a1f6 100644 --- a/x-pack/test/examples/search_examples/search_sessions_cache.ts +++ b/x-pack/test/examples/search_examples/search_sessions_cache.ts @@ -27,7 +27,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { return text; } - describe('Search session client side cache', () => { + // FLAKY: https://github.com/elastic/kibana/issues/116537 + describe.skip('Search session client side cache', () => { const appId = 'searchExamples'; before(async function () { diff --git a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts index f7510c3c3031..95ddd0a7b594 100644 --- a/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts +++ b/x-pack/test/functional/apps/index_lifecycle_management/home_page.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; const policyName = 'testPolicy1'; +const repoName = 'test'; export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'indexLifecycleManagement']); @@ -16,12 +17,23 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const retry = getService('retry'); const esClient = getService('es'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/114473 and https://github.com/elastic/kibana/issues/114474 - describe.skip('Home page', function () { + describe('Home page', function () { before(async () => { + await esClient.snapshot.createRepository({ + name: repoName, + body: { + type: 'fs', + settings: { + // use one of the values defined in path.repo in test/functional/config.js + location: '/tmp/', + }, + }, + verify: false, + }); await pageObjects.common.navigateToApp('indexLifecycleManagement'); }); after(async () => { + await esClient.snapshot.deleteRepository({ name: repoName }); await esClient.ilm.deleteLifecycle({ name: policyName }); }); @@ -41,6 +53,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { coldEnabled: true, frozenEnabled: true, deleteEnabled: true, + snapshotRepository: repoName, }); await retry.waitFor('navigation back to home page.', async () => { diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/forecasts.ts b/x-pack/test/functional/apps/ml/anomaly_detection/forecasts.ts new file mode 100644 index 000000000000..f65653e2c03c --- /dev/null +++ b/x-pack/test/functional/apps/ml/anomaly_detection/forecasts.ts @@ -0,0 +1,116 @@ +/* + * 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'; +import { Job, Datafeed } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; + +// @ts-expect-error not full interface +const JOB_CONFIG: Job = { + job_id: `fq_single_1_smv`, + description: 'count() on farequote dataset with 15m bucket span', + groups: ['farequote', 'automated', 'single-metric'], + analysis_config: { + bucket_span: '15m', + influencers: [], + detectors: [ + { + function: 'count', + }, + ], + }, + data_description: { time_field: '@timestamp' }, + analysis_limits: { model_memory_limit: '10mb' }, + model_plot_config: { enabled: true }, +}; + +// @ts-expect-error not full interface +const DATAFEED_CONFIG: Datafeed = { + datafeed_id: 'datafeed-fq_single_1_smv', + indices: ['ft_farequote'], + job_id: 'fq_single_1_smv', + query: { bool: { must: [{ match_all: {} }] } }, +}; + +export default function ({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + describe('forecasts', function () { + this.tags(['mlqa']); + + describe('with single metric job', function () { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); + await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.setKibanaTimeZoneToUTC(); + + await ml.api.createAndRunAnomalyDetectionLookbackJob(JOB_CONFIG, DATAFEED_CONFIG); + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + it('opens a job from job list link', async () => { + await ml.testExecution.logTestStep('navigate to job list'); + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.testExecution.logTestStep('open job in single metric viewer'); + await ml.jobTable.waitForJobsToLoad(); + await ml.jobTable.filterWithSearchString(JOB_CONFIG.job_id, 1); + + await ml.jobTable.clickOpenJobInSingleMetricViewerButton(JOB_CONFIG.job_id); + await ml.commonUI.waitForMlLoadingIndicatorToDisappear(); + }); + + it('displays job results', async () => { + await ml.testExecution.logTestStep('pre-fills the job selection'); + await ml.jobSelection.assertJobSelection([JOB_CONFIG.job_id]); + + await ml.testExecution.logTestStep('pre-fills the detector input'); + await ml.singleMetricViewer.assertDetectorInputExist(); + await ml.singleMetricViewer.assertDetectorInputValue('0'); + + await ml.testExecution.logTestStep('displays the chart'); + await ml.singleMetricViewer.assertChartExist(); + + await ml.testExecution.logTestStep('should not display the forecasts toggle checkbox'); + await ml.forecast.assertForecastCheckboxMissing(); + + await ml.testExecution.logTestStep('should open the forecasts modal'); + await ml.forecast.assertForecastButtonExists(); + await ml.forecast.assertForecastButtonEnabled(true); + await ml.forecast.openForecastModal(); + await ml.forecast.assertForecastModalRunButtonEnabled(true); + + await ml.testExecution.logTestStep('should run the forecast and close the modal'); + await ml.forecast.clickForecastModalRunButton(); + + await ml.testExecution.logTestStep('should display the forecasts toggle checkbox'); + await ml.forecast.assertForecastCheckboxExists(); + + await ml.testExecution.logTestStep( + 'should display the forecast in the single metric chart' + ); + await ml.forecast.assertForecastChartElementsExists(); + + await ml.testExecution.logTestStep('should hide the forecast in the single metric chart'); + await ml.forecast.clickForecastCheckbox(); + await ml.forecast.assertForecastChartElementsHidden(); + + await ml.testExecution.logTestStep('should open the forecasts modal and list the forecast'); + await ml.forecast.assertForecastButtonExists(); + await ml.forecast.assertForecastButtonEnabled(true); + await ml.forecast.openForecastModal(); + await ml.forecast.assertForecastTableExists(); + await ml.forecast.assertForecastTableNotEmpty(); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/ml/anomaly_detection/index.ts b/x-pack/test/functional/apps/ml/anomaly_detection/index.ts index d87da8469db1..ed5f618f8664 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection/index.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection/index.ts @@ -24,5 +24,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./annotations')); loadTestFile(require.resolve('./aggregated_scripted_job')); loadTestFile(require.resolve('./custom_urls')); + loadTestFile(require.resolve('./forecasts')); }); } diff --git a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts index 448774f1a0c7..356e38221796 100644 --- a/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/full_ml_access.ts @@ -237,11 +237,11 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep( 'should display the forecast modal with enabled run button' ); - await ml.singleMetricViewer.assertForecastButtonExists(); - await ml.singleMetricViewer.assertForecastButtonEnabled(true); - await ml.singleMetricViewer.openForecastModal(); - await ml.singleMetricViewer.assertForecastModalRunButtonEnabled(true); - await ml.singleMetricViewer.closeForecastModal(); + await ml.forecast.assertForecastButtonExists(); + await ml.forecast.assertForecastButtonEnabled(true); + await ml.forecast.openForecastModal(); + await ml.forecast.assertForecastModalRunButtonEnabled(true); + await ml.forecast.closeForecastModal(); }); it('should display elements on Anomaly Explorer page correctly', async () => { diff --git a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts index b96da7485078..be57904b9445 100644 --- a/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts +++ b/x-pack/test/functional/apps/ml/permissions/read_ml_access.ts @@ -230,11 +230,11 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep( 'should display the forecast modal with disabled run button' ); - await ml.singleMetricViewer.assertForecastButtonExists(); - await ml.singleMetricViewer.assertForecastButtonEnabled(true); - await ml.singleMetricViewer.openForecastModal(); - await ml.singleMetricViewer.assertForecastModalRunButtonEnabled(false); - await ml.singleMetricViewer.closeForecastModal(); + await ml.forecast.assertForecastButtonExists(); + await ml.forecast.assertForecastButtonEnabled(true); + await ml.forecast.openForecastModal(); + await ml.forecast.assertForecastModalRunButtonEnabled(false); + await ml.forecast.closeForecastModal(); }); it('should display elements on Anomaly Explorer page correctly', async () => { diff --git a/x-pack/test/functional/services/ml/forecast.ts b/x-pack/test/functional/services/ml/forecast.ts new file mode 100644 index 000000000000..c26216c97adf --- /dev/null +++ b/x-pack/test/functional/services/ml/forecast.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function MachineLearningForecastProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + + return { + async assertForecastButtonExists() { + await testSubjects.existOrFail( + 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' + ); + }, + + async assertForecastButtonEnabled(expectedValue: boolean) { + const isEnabled = await testSubjects.isEnabled( + 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' + ); + expect(isEnabled).to.eql( + expectedValue, + `Expected "forecast" button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ + isEnabled ? 'enabled' : 'disabled' + }')` + ); + }, + + async assertForecastChartElementsExists() { + await testSubjects.existOrFail(`mlForecastArea`, { + timeout: 30 * 1000, + }); + await testSubjects.existOrFail(`mlForecastValuesline`, { + timeout: 30 * 1000, + }); + await testSubjects.existOrFail(`mlForecastMarkers`, { + timeout: 30 * 1000, + }); + }, + + async assertForecastChartElementsHidden() { + await testSubjects.missingOrFail(`mlForecastArea`, { + allowHidden: true, + timeout: 30 * 1000, + }); + await testSubjects.missingOrFail(`mlForecastValuesline`, { + allowHidden: true, + timeout: 30 * 1000, + }); + await testSubjects.missingOrFail(`mlForecastMarkers`, { + allowHidden: true, + timeout: 30 * 1000, + }); + }, + + async assertForecastCheckboxExists() { + await testSubjects.existOrFail(`mlForecastCheckbox`, { + timeout: 30 * 1000, + }); + }, + + async assertForecastCheckboxMissing() { + await testSubjects.missingOrFail(`mlForecastCheckbox`, { + timeout: 30 * 1000, + }); + }, + + async clickForecastCheckbox() { + await testSubjects.click('mlForecastCheckbox'); + }, + + async openForecastModal() { + await testSubjects.click( + 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' + ); + await testSubjects.existOrFail('mlModalForecast'); + }, + + async closeForecastModal() { + await testSubjects.click('mlModalForecast > mlModalForecastButtonClose'); + await this.assertForecastModalMissing(); + }, + + async assertForecastModalMissing() { + await testSubjects.missingOrFail(`mlModalForecast`, { + timeout: 30 * 1000, + }); + }, + + async assertForecastModalRunButtonEnabled(expectedValue: boolean) { + const isEnabled = await testSubjects.isEnabled('mlModalForecast > mlModalForecastButtonRun'); + expect(isEnabled).to.eql( + expectedValue, + `Expected forecast "run" button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ + isEnabled ? 'enabled' : 'disabled' + }')` + ); + }, + + async assertForecastTableExists() { + await testSubjects.existOrFail('mlModalForecast > mlModalForecastTable'); + }, + + async clickForecastModalRunButton() { + await testSubjects.click('mlModalForecast > mlModalForecastButtonRun'); + await this.assertForecastModalMissing(); + }, + + async getForecastTableRows() { + return await testSubjects.findAll('mlModalForecastTable > ~mlForecastsListRow'); + }, + + async assertForecastTableNotEmpty() { + const tableRows = await this.getForecastTableRows(); + expect(tableRows.length).to.be.greaterThan( + 0, + `Forecast table should have at least one row (got '${tableRows.length}')` + ); + }, + }; +} diff --git a/x-pack/test/functional/services/ml/index.ts b/x-pack/test/functional/services/ml/index.ts index 17302b278222..4b48e4c0269e 100644 --- a/x-pack/test/functional/services/ml/index.ts +++ b/x-pack/test/functional/services/ml/index.ts @@ -24,6 +24,7 @@ import { MachineLearningDataVisualizerProvider } from './data_visualizer'; import { MachineLearningDataVisualizerFileBasedProvider } from './data_visualizer_file_based'; import { MachineLearningDataVisualizerIndexBasedProvider } from './data_visualizer_index_based'; import { MachineLearningDataVisualizerIndexPatternManagementProvider } from './data_visualizer_index_pattern_management'; +import { MachineLearningForecastProvider } from './forecast'; import { MachineLearningJobManagementProvider } from './job_management'; import { MachineLearningJobSelectionProvider } from './job_selection'; import { MachineLearningJobSourceSelectionProvider } from './job_source_selection'; @@ -92,6 +93,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { const dataVisualizerIndexPatternManagement = MachineLearningDataVisualizerIndexPatternManagementProvider(context, dataVisualizerTable); + const forecast = MachineLearningForecastProvider(context); const jobAnnotations = MachineLearningJobAnnotationsProvider(context); const jobManagement = MachineLearningJobManagementProvider(context, api); const jobSelection = MachineLearningJobSelectionProvider(context); @@ -145,6 +147,7 @@ export function MachineLearningProvider(context: FtrProviderContext) { dataVisualizerIndexBased, dataVisualizerIndexPatternManagement, dataVisualizerTable, + forecast, jobAnnotations, jobManagement, jobSelection, diff --git a/x-pack/test/functional/services/ml/security_common.ts b/x-pack/test/functional/services/ml/security_common.ts index 54d2fa48a826..925565143bda 100644 --- a/x-pack/test/functional/services/ml/security_common.ts +++ b/x-pack/test/functional/services/ml/security_common.ts @@ -58,7 +58,7 @@ export function MachineLearningSecurityCommonProvider({ getService }: FtrProvide { name: 'ft_ml_ui_extras', elasticsearch: { - cluster: ['manage_ingest_pipelines', 'monitor'], + cluster: ['manage_ingest_pipelines'], }, kibana: [], }, diff --git a/x-pack/test/functional/services/ml/single_metric_viewer.ts b/x-pack/test/functional/services/ml/single_metric_viewer.ts index ac3fd67e3f94..29f1ded74deb 100644 --- a/x-pack/test/functional/services/ml/single_metric_viewer.ts +++ b/x-pack/test/functional/services/ml/single_metric_viewer.ts @@ -22,24 +22,6 @@ export function MachineLearningSingleMetricViewerProvider( await testSubjects.existOrFail('mlNoSingleMetricJobsFound'); }, - async assertForecastButtonExists() { - await testSubjects.existOrFail( - 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' - ); - }, - - async assertForecastButtonEnabled(expectedValue: boolean) { - const isEnabled = await testSubjects.isEnabled( - 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' - ); - expect(isEnabled).to.eql( - expectedValue, - `Expected "forecast" button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ - isEnabled ? 'enabled' : 'disabled' - }')` - ); - }, - async assertDetectorInputExist() { await testSubjects.existOrFail( 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerDetectorSelect' @@ -97,28 +79,6 @@ export function MachineLearningSingleMetricViewerProvider( }); }, - async openForecastModal() { - await testSubjects.click( - 'mlSingleMetricViewerSeriesControls > mlSingleMetricViewerButtonForecast' - ); - await testSubjects.existOrFail('mlModalForecast'); - }, - - async closeForecastModal() { - await testSubjects.click('mlModalForecast > mlModalForecastButtonClose'); - await testSubjects.missingOrFail('mlModalForecast'); - }, - - async assertForecastModalRunButtonEnabled(expectedValue: boolean) { - const isEnabled = await testSubjects.isEnabled('mlModalForecast > mlModalForecastButtonRun'); - expect(isEnabled).to.eql( - expectedValue, - `Expected forecast "run" button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ - isEnabled ? 'enabled' : 'disabled' - }')` - ); - }, - async openAnomalyExplorer() { await testSubjects.click('mlAnomalyResultsViewSelectorExplorer'); await testSubjects.existOrFail('mlPageAnomalyExplorer'); diff --git a/x-pack/test/lists_api_integration/utils.ts b/x-pack/test/lists_api_integration/utils.ts index c8c1acb9f0e8..eda32c7fe9fb 100644 --- a/x-pack/test/lists_api_integration/utils.ts +++ b/x-pack/test/lists_api_integration/utils.ts @@ -210,12 +210,20 @@ export const importFile = async ( fileName: string, testValues?: string[] ): Promise => { - await supertest + const response = await supertest .post(`${LIST_ITEM_URL}/_import?type=${type}`) .set('kbn-xsrf', 'true') .attach('file', getImportListItemAsBuffer(contents), fileName) - .expect('Content-Type', 'application/json; charset=utf-8') - .expect(200); + .expect('Content-Type', 'application/json; charset=utf-8'); + + if (response.status !== 200) { + // eslint-disable-next-line no-console + console.log( + `Did not get an expected 200 "ok" When importing a file. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } // although we have pushed the list and its items, it is async so we // have to wait for the contents before continuing diff --git a/x-pack/test/load/runner.ts b/x-pack/test/load/runner.ts index 0bea5992f553..c48a8e33d6ee 100644 --- a/x-pack/test/load/runner.ts +++ b/x-pack/test/load/runner.ts @@ -28,7 +28,11 @@ if (!Fs.existsSync(gatlingProjectRootPath)) { ); } -const dropEmptyLines = (s: string) => s.split(',').filter((i) => i.length > 0); +const dropEmptyLines = (s: string) => + s + .split(',') + .filter((i) => i.length > 0) + .map((i) => (i.includes('.') ? i : `branch.${i}`)); const simulationClasses = dropEmptyLines(simulationEntry); const simulationsRootPath = resolve(gatlingProjectRootPath, baseSimulationPath); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 323b08dd88be..6ac54750c6ec 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -6,12 +6,19 @@ */ import expect from '@kbn/expect'; +import { DeepPartial } from 'utility-types'; +import { merge } from 'lodash'; import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../services/endpoint_policy'; import { IndexedHostsAndAlertsResponse } from '../../../../plugins/security_solution/common/endpoint/index_data'; +import { FullAgentPolicyInput } from '../../../../plugins/fleet/common'; +import { PolicyConfig } from '../../../../plugins/security_solution/common/endpoint/types'; +import { ManifestSchema } from '../../../../plugins/security_solution/common/endpoint/schema/manifest'; +import { policyFactory } from '../../../../plugins/security_solution/common/endpoint/models/policy_config'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const browser = getService('browser'); + const retryService = getService('retry'); const pageObjects = getPageObjects([ 'common', 'endpoint', @@ -24,18 +31,224 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const policyTestResources = getService('policyTestResources'); const endpointTestResources = getService('endpointTestResources'); - // FLAKY https://github.com/elastic/kibana/issues/100296 - describe.skip('When on the Endpoint Policy Details Page', function () { + type FullAgentPolicyEndpointInput = Omit & { + policy: PolicyConfig; + artifact_manifest: ManifestSchema; + }; + + /** + * Returns the Fleet Agent Policy Input that represents an Endpoint Policy. Use it to + * validate expecte output when looking at the Fleet Agent policy to validate that updates + * to the Endpoint Policy are making it through to the overall Fleet Agent Policy + * + * @param overrides + */ + const getExpectedAgentPolicyEndpointInput = ( + overrides: DeepPartial = {} + ): FullAgentPolicyInput => { + return merge( + { + id: '123', + revision: 2, + data_stream: { namespace: 'default' }, + name: 'Protect East Coast', + meta: { + package: { + name: 'endpoint', + version: '1.0', + }, + }, + artifact_manifest: { + artifacts: { + 'endpoint-exceptionlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-exceptionlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-exceptionlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-hostisolationexceptionlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-hostisolationexceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-hostisolationexceptionlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-hostisolationexceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-hostisolationexceptionlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-hostisolationexceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-trustlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-trustlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-trustlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-linux-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-macos-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + 'endpoint-eventfilterlist-windows-v1': { + compression_algorithm: 'zlib', + decoded_sha256: 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + decoded_size: 14, + encoded_sha256: 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', + encoded_size: 22, + encryption_algorithm: 'none', + relative_url: + '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', + }, + }, + manifest_version: '1', + schema_version: 'v1', + }, + policy: merge(policyFactory(), { + windows: { + popup: { + malware: { + message: 'Elastic Security {action} {filename}', + }, + ransomware: { + message: 'Elastic Security {action} {filename}', + }, + memory_protection: { + message: 'Elastic Security {action} {rule}', + }, + behavior_protection: { + message: 'Elastic Security {action} {rule}', + }, + }, + }, + mac: { + popup: { + malware: { + message: 'Elastic Security {action} {filename}', + }, + behavior_protection: { + message: 'Elastic Security {action} {rule}', + }, + memory_protection: { + message: 'Elastic Security {action} {rule}', + }, + }, + }, + linux: { + popup: { + malware: { + message: 'Elastic Security {action} {filename}', + }, + behavior_protection: { + message: 'Elastic Security {action} {rule}', + }, + memory_protection: { + message: 'Elastic Security {action} {rule}', + }, + }, + }, + }), + type: 'endpoint', + use_output: 'default', + }, + overrides + ); + }; + + describe('When on the Endpoint Policy Details Page', function () { let indexedData: IndexedHostsAndAlertsResponse; + before(async () => { const endpointPackage = await policyTestResources.getEndpointPackage(); await endpointTestResources.setMetadataTransformFrequency('1s', endpointPackage.version); indexedData = await endpointTestResources.loadEndpointData(); await browser.refresh(); }); + after(async () => { await endpointTestResources.unloadEndpointData(indexedData); }); + describe('with an invalid policy id', () => { it('should display an error', async () => { await pageObjects.policy.navigateToPolicyDetails('invalid-id'); @@ -69,13 +282,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('and the show advanced settings button is clicked', async () => { await testSubjects.missingOrFail('advancedPolicyPanel'); - let advancedPolicyButton = await pageObjects.policy.findAdvancedPolicyButton(); - await advancedPolicyButton.click(); - + // Expand + await pageObjects.policy.showAdvancedSettingsSection(); await testSubjects.existOrFail('advancedPolicyPanel'); - advancedPolicyButton = await pageObjects.policy.findAdvancedPolicyButton(); - await advancedPolicyButton.click(); + // Collapse + await pageObjects.policy.hideAdvancedSettingsSection(); await testSubjects.missingOrFail('advancedPolicyPanel'); }); }); @@ -99,11 +311,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(await testSubjects.isChecked('malwareUserNotificationCheckbox')).to.be(true); await testSubjects.existOrFail('malwareUserNotificationCustomMessage'); }); + it('should not show the custom message text area when the Notify User checkbox is unchecked', async () => { await pageObjects.endpointPageUtils.clickOnEuiCheckbox('malwareUserNotificationCheckbox'); expect(await testSubjects.isChecked('malwareUserNotificationCheckbox')).to.be(false); await testSubjects.missingOrFail('malwareUserNotificationCustomMessage'); }); + it('should preserve a custom notification message upon saving', async () => { const customMessage = await testSubjects.find('malwareUserNotificationCustomMessage'); await customMessage.clearValue(); @@ -139,11 +353,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { `Integration ${policyInfo.packagePolicy.name} has been updated.` ); }); + it('should persist update on the screen', async () => { await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_process'); await pageObjects.policy.confirmAndSave(); await testSubjects.existOrFail('policyDetailsSuccessMessage'); + await pageObjects.common.closeToast(); await pageObjects.endpoint.navigateToEndpointList(); await pageObjects.policy.navigateToPolicyDetails(policyInfo.packagePolicy.id); @@ -151,6 +367,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { false ); }); + it('should have updated policy data in overall Agent Policy', async () => { // This test ensures that updates made to the Endpoint Policy are carried all the way through // to the generated Agent Policy that is dispatch down to the Elastic Agent. @@ -161,8 +378,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyMacEvent_file'), ]); - const advancedPolicyButton = await pageObjects.policy.findAdvancedPolicyButton(); - await advancedPolicyButton.click(); + await pageObjects.policy.showAdvancedSettingsSection(); const advancedPolicyField = await pageObjects.policy.findAdvancedPolicyField(); await advancedPolicyField.clearValue(); @@ -177,226 +393,38 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); expect(agentFullPolicy.inputs).to.eql([ - { + getExpectedAgentPolicyEndpointInput({ id: policyInfo.packagePolicy.id, - revision: 2, - data_stream: { namespace: 'default' }, - name: 'Protect East Coast', meta: { package: { - name: 'endpoint', version: policyInfo.packageInfo.version, }, }, artifact_manifest: { - artifacts: { - 'endpoint-exceptionlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - }, - // The manifest version could have changed when the Policy was updated because the - // policy details page ensures that a save action applies the udpated policy on top - // of the latest Package Policy. So we just ignore the check against this value by - // forcing it to be the same as the value returned in the full agent policy. manifest_version: agentFullPolicy.inputs[0].artifact_manifest.manifest_version, - schema_version: 'v1', }, policy: { linux: { - events: { file: false, network: true, process: true }, - logging: { file: 'info' }, - advanced: { agent: { connection_delay: 'true' } }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - }, - }, - mac: { - events: { file: false, network: true, process: true }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - }, - }, - windows: { events: { - dll_and_driver_load: true, - dns: true, file: false, - network: true, - process: true, - registry: true, - security: true, }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - memory_protection: { mode: 'prevent', supported: true }, - behavior_protection: { mode: 'prevent', supported: true }, - ransomware: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', + advanced: { + agent: { + connection_delay: 'true', }, - ransomware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - }, - antivirus_registration: { - enabled: false, }, }, + mac: { + events: { file: false }, + }, + windows: { events: { file: false } }, }, - type: 'endpoint', - use_output: 'default', - }, + }), ]); }); it('should have cleared the advanced section when the user deletes the value', async () => { - const advancedPolicyButton = await pageObjects.policy.findAdvancedPolicyButton(); - await advancedPolicyButton.click(); + await pageObjects.policy.showAdvancedSettingsSection(); const advancedPolicyField = await pageObjects.policy.findAdvancedPolicyField(); await advancedPolicyField.clearValue(); @@ -411,220 +439,26 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); expect(agentFullPolicy.inputs).to.eql([ - { + getExpectedAgentPolicyEndpointInput({ id: policyInfo.packagePolicy.id, - revision: 2, - data_stream: { namespace: 'default' }, - name: 'Protect East Coast', meta: { package: { - name: 'endpoint', version: policyInfo.packageInfo.version, }, }, artifact_manifest: { - artifacts: { - 'endpoint-exceptionlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - }, - // The manifest version could have changed when the Policy was updated because the - // policy details page ensures that a save action applies the udpated policy on top - // of the latest Package Policy. So we just ignore the check against this value by - // forcing it to be the same as the value returned in the full agent policy. manifest_version: agentFullPolicy.inputs[0].artifact_manifest.manifest_version, - schema_version: 'v1', }, policy: { linux: { - events: { file: true, network: true, process: true }, - logging: { file: 'info' }, - advanced: { agent: { connection_delay: 'true' } }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', + advanced: { + agent: { + connection_delay: 'true', }, }, }, - mac: { - events: { file: true, network: true, process: true }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - }, - }, - windows: { - events: { - dll_and_driver_load: true, - dns: true, - file: true, - network: true, - process: true, - registry: true, - security: true, - }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - memory_protection: { mode: 'prevent', supported: true }, - behavior_protection: { mode: 'prevent', supported: true }, - ransomware: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - ransomware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - }, - antivirus_registration: { - enabled: false, - }, - }, }, - type: 'endpoint', - use_output: 'default', - }, + }), ]); // Clear the value @@ -643,225 +477,25 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); expect(agentFullPolicyUpdated.inputs).to.eql([ - { + getExpectedAgentPolicyEndpointInput({ id: policyInfo.packagePolicy.id, revision: 3, - data_stream: { namespace: 'default' }, - name: 'Protect East Coast', meta: { package: { - name: 'endpoint', version: policyInfo.packageInfo.version, }, }, artifact_manifest: { - artifacts: { - 'endpoint-exceptionlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-exceptionlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-exceptionlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-trustlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-trustlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-linux-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-linux-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-macos-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-macos-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - 'endpoint-eventfilterlist-windows-v1': { - compression_algorithm: 'zlib', - decoded_sha256: - 'd801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - decoded_size: 14, - encoded_sha256: - 'f8e6afa1d5662f5b37f83337af774b5785b5b7f1daee08b7b00c2d6813874cda', - encoded_size: 22, - encryption_algorithm: 'none', - relative_url: - '/api/fleet/artifacts/endpoint-eventfilterlist-windows-v1/d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658', - }, - }, - // The manifest version could have changed when the Policy was updated because the - // policy details page ensures that a save action applies the udpated policy on top - // of the latest Package Policy. So we just ignore the check against this value by - // forcing it to be the same as the value returned in the full agent policy. manifest_version: agentFullPolicy.inputs[0].artifact_manifest.manifest_version, - schema_version: 'v1', }, - policy: { - linux: { - events: { file: true, network: true, process: true }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - }, - }, - mac: { - events: { file: true, network: true, process: true }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - behavior_protection: { mode: 'prevent', supported: true }, - memory_protection: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - }, - }, - windows: { - events: { - dll_and_driver_load: true, - dns: true, - file: true, - network: true, - process: true, - registry: true, - security: true, - }, - logging: { file: 'info' }, - malware: { mode: 'prevent' }, - memory_protection: { mode: 'prevent', supported: true }, - behavior_protection: { mode: 'prevent', supported: true }, - ransomware: { mode: 'prevent', supported: true }, - popup: { - malware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - memory_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - behavior_protection: { - enabled: true, - message: 'Elastic Security {action} {rule}', - }, - ransomware: { - enabled: true, - message: 'Elastic Security {action} {filename}', - }, - }, - antivirus_registration: { - enabled: false, - }, - }, - }, - type: 'endpoint', - use_output: 'default', - }, + }), ]); }); }); describe('when on Ingest Policy Edit Package Policy page', async () => { let policyInfo: PolicyTestResourceInfo; + beforeEach(async () => { // Create a policy and navigate to Ingest app policyInfo = await policyTestResources.createPolicy(); @@ -871,6 +505,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await testSubjects.existOrFail('endpointIntegrationPolicyForm'); }); + afterEach(async () => { if (policyInfo) { await policyInfo.cleanup(); @@ -888,10 +523,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); expect(await winDnsEventingCheckbox.isSelected()).to.be(true); await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns'); - expect(await winDnsEventingCheckbox.isSelected()).to.be(false); + await pageObjects.policy.waitForCheckboxSelectionChange('policyWindowsEvent_dns', false); }); it('should preserve updates done from the Fleet form', async () => { + // Fleet has its own form inputs, like description. When those are updated, the changes + // are also dispatched to the embedded endpoint Policy form. Update to the Endpoint Policy + // form after that should preserve the changes done on the Fleet form + + // Wait for the endpoint form to load and then update the policy description + await testSubjects.existOrFail('endpointIntegrationPolicyForm'); await pageObjects.ingestManagerCreatePackagePolicy.setPackagePolicyDescription( 'protect everything' ); @@ -902,18 +543,22 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns'); - expect( - await pageObjects.ingestManagerCreatePackagePolicy.getPackagePolicyDescriptionValue() - ).to.be('protect everything'); + await retryService.try(async () => { + expect( + await pageObjects.ingestManagerCreatePackagePolicy.getPackagePolicyDescriptionValue() + ).to.be('protect everything'); + }); }); it('should include updated endpoint data when saved', async () => { - const winDnsEventingCheckbox = await testSubjects.find('policyWindowsEvent_dns'); await pageObjects.ingestManagerCreatePackagePolicy.scrollToCenterOfWindow( - winDnsEventingCheckbox + await testSubjects.find('policyWindowsEvent_dns') ); await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns'); - const wasSelected = await winDnsEventingCheckbox.isSelected(); + const updatedCheckboxValue = await testSubjects.isSelected('policyWindowsEvent_dns'); + + await pageObjects.policy.waitForCheckboxSelectionChange('policyWindowsEvent_dns', false); + await (await pageObjects.ingestManagerCreatePackagePolicy.findSaveButton(true)).click(); await pageObjects.ingestManagerCreatePackagePolicy.waitForSaveSuccessNotification(true); @@ -921,7 +566,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { policyInfo.agentPolicy.id, policyInfo.packagePolicy.id ); - expect(await testSubjects.isSelected('policyWindowsEvent_dns')).to.be(wasSelected); + + await pageObjects.policy.waitForCheckboxSelectionChange( + 'policyWindowsEvent_dns', + updatedCheckboxValue + ); }); it('should show trusted apps card and link should go back to policy', async () => { diff --git a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts index d1a037a47ff0..b5eccd0ef114 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts @@ -5,11 +5,13 @@ * 2.0. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../ftr_provider_context'; export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrProviderContext) { const pageObjects = getPageObjects(['common', 'header']); const testSubjects = getService('testSubjects'); + const retryService = getService('retry'); return { /** @@ -49,6 +51,34 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr return await testSubjects.find('advancedPolicyButton'); }, + async isAdvancedSettingsExpanded() { + return await testSubjects.exists('advancedPolicyPanel'); + }, + + /** + * shows the advanced settings section and scrolls it into view + */ + async showAdvancedSettingsSection() { + if (!(await this.isAdvancedSettingsExpanded())) { + const expandButton = await this.findAdvancedPolicyButton(); + await expandButton.click(); + } + + await testSubjects.existOrFail('advancedPolicyPanel'); + await testSubjects.scrollIntoView('advancedPolicyPanel'); + }, + + /** + * Hides the advanced settings section + */ + async hideAdvancedSettingsSection() { + if (await this.isAdvancedSettingsExpanded()) { + const expandButton = await this.findAdvancedPolicyButton(); + await expandButton.click(); + } + await testSubjects.missingOrFail('advancedPolicyPanel'); + }, + /** * Finds and returns the linux connection_delay Advanced Policy field */ @@ -69,7 +99,17 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr */ async confirmAndSave() { await this.ensureIsOnDetailsPage(); - await (await this.findSaveButton()).click(); + + const saveButton = await this.findSaveButton(); + + // Sometimes, data retrieval errors may have been encountered by other security solution processes + // (ex. index fields search here: `x-pack/plugins/security_solution/public/common/containers/source/index.tsx:181`) + // which are displayed using one or more Toast messages. This in turn prevents the user from + // actually clicking the Save button. Because those errors are not associated with Policy details, + // we'll first check that all toasts are cleared + await pageObjects.common.clearAllToasts(); + + await saveButton.click(); await testSubjects.existOrFail('policyDetailsConfirmModal'); await pageObjects.common.clickConfirmOnModal(); }, @@ -93,5 +133,19 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr async findPackagePolicyEndpointCustomConfiguration(onEditPage: boolean = false) { return await testSubjects.find(`endpointPackagePolicy_${onEditPage ? 'edit' : 'create'}`); }, + + /** + * Waits for a Checkbox/Radiobutton to have its `isSelected()` value match the provided expected value + * @param selector + * @param expectedSelectedValue + */ + async waitForCheckboxSelectionChange( + selector: string, + expectedSelectedValue: boolean + ): Promise { + await retryService.try(async () => { + expect(await testSubjects.isSelected(selector)).to.be(expectedSelectedValue); + }); + }, }; } diff --git a/x-pack/test/usage_collection/test_suites/application_usage/index.ts b/x-pack/test/usage_collection/test_suites/application_usage/index.ts index fc53c8ddf5ed..4ba45b4bf9e1 100644 --- a/x-pack/test/usage_collection/test_suites/application_usage/index.ts +++ b/x-pack/test/usage_collection/test_suites/application_usage/index.ts @@ -10,7 +10,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { applicationUsageSchema } from '../../../../../src/plugins/kibana_usage_collection/server/collectors/application_usage/schema'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - describe('Application Usage', function () { + // FLAKY: https://github.com/elastic/kibana/issues/90536 + describe.skip('Application Usage', function () { this.tags('ciGroup1'); const { common } = getPageObjects(['common']); const browser = getService('browser');