diff --git a/.buildkite/package-lock.json b/.buildkite/package-lock.json index 401f25347ebc0..b782b9c1d34eb 100644 --- a/.buildkite/package-lock.json +++ b/.buildkite/package-lock.json @@ -696,9 +696,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -2200,9 +2200,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "form-data": { "version": "4.0.0", diff --git a/.buildkite/scripts/steps/code_coverage/ingest.sh b/.buildkite/scripts/steps/code_coverage/ingest.sh index 6940c413f0e93..d2a4bd55674cf 100755 --- a/.buildkite/scripts/steps/code_coverage/ingest.sh +++ b/.buildkite/scripts/steps/code_coverage/ingest.sh @@ -3,15 +3,16 @@ set -euo pipefail source .buildkite/scripts/common/util.sh +source .buildkite/scripts/common/vault_fns.sh source .buildkite/scripts/steps/code_coverage/util.sh export CODE_COVERAGE=1 -echo "--- Reading Kibana stats cluster creds from vault" -USER_FROM_VAULT="$(retry 5 5 vault read -field=username secret/kibana-issues/prod/coverage/elasticsearch)" +echo "--- Reading Kibana coverage creds from vault" +USER_FROM_VAULT="$(vault_get coverage/elasticsearch username)" export USER_FROM_VAULT -PASS_FROM_VAULT="$(retry 5 5 vault read -field=password secret/kibana-issues/prod/coverage/elasticsearch)" +PASS_FROM_VAULT="$(vault_get coverage/elasticsearch password)" export PASS_FROM_VAULT -HOST_FROM_VAULT="$(retry 5 5 vault read -field=host secret/kibana-issues/prod/coverage/elasticsearch)" +HOST_FROM_VAULT="$(vault_get coverage/elasticsearch host)" export HOST_FROM_VAULT TIME_STAMP=$(date +"%Y-%m-%dT%H:%M:00Z") export TIME_STAMP diff --git a/docs/management/connectors/action-types/cases-webhook.asciidoc b/docs/management/connectors/action-types/cases-webhook.asciidoc index 8610c4593d513..3d5bfecffcb3b 100644 --- a/docs/management/connectors/action-types/cases-webhook.asciidoc +++ b/docs/management/connectors/action-types/cases-webhook.asciidoc @@ -53,6 +53,7 @@ image::management/connectors/images/cases-webhook-connector-comments.png[Add cas Add HTTP header:: A set of key-value pairs sent as headers with the request URLs for the create case, update case, get case, and create comment methods. +For example, set `Content-Type` to the appropriate media type for your requests. Create case method:: The REST API HTTP request method to create a case in the third-party system: `post`(default), `put`, or `patch`. diff --git a/docs/management/connectors/action-types/webhook.asciidoc b/docs/management/connectors/action-types/webhook.asciidoc index 1868f96d14c1d..d9a4dd43daeaf 100644 --- a/docs/management/connectors/action-types/webhook.asciidoc +++ b/docs/management/connectors/action-types/webhook.asciidoc @@ -34,7 +34,9 @@ Authentication:: The authentication type: none, basic, or SSL. If you choose basic authentication, you must provide a user name and password. If you choose SSL authentication, you must provide SSL server certificate authentication data in a CRT and key file format or a PFX file format. You can also optionally provide a passphrase if the files are password-protected. -HTTP headers:: A set of key-value pairs sent as headers with the request. +HTTP headers:: +A set of key-value pairs sent as headers with the request. +For example, set `Content-Type` to the appropriate media type for your requests. Certificate authority:: A certificate authority (CA) that the connector can trust, for example to sign and validate server certificates. This option is available for all authentication types. diff --git a/package.json b/package.json index 816332219392a..d0f1c3a68daae 100644 --- a/package.json +++ b/package.json @@ -1519,6 +1519,7 @@ "@types/vinyl-fs": "^3.0.2", "@types/watchpack": "^1.1.5", "@types/webpack": "^4.41.3", + "@types/webpack-bundle-analyzer": "^4.7.0", "@types/webpack-env": "^1.15.3", "@types/webpack-merge": "^4.1.5", "@types/webpack-sources": "^0.1.4", diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/constants.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/constants.ts index 124515299efc7..0a09032d290e5 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/constants.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/constants.ts @@ -180,7 +180,7 @@ export const HASH_TO_VERSION_MAP = { 'infrastructure-monitoring-log-view|c50526fc6040c5355ed027d34d05b35c': '10.0.0', 'infrastructure-ui-source|3d1b76c39bfb2cc8296b024d73854724': '10.0.0', 'ingest_manager_settings|b91ffb075799c78ffd7dbd51a279c8c9': '10.1.0', - 'ingest-agent-policies|20768dc7ce5eced3eb309e50d8a6cf76': '10.0.0', + 'ingest-agent-policies|0fd93cd11c019b118e93a9157c22057b': '10.1.0', 'ingest-download-sources|0b0f6828e59805bd07a650d80817c342': '10.0.0', 'ingest-outputs|b1237f7fdc0967709e75d65d208ace05': '10.6.0', 'ingest-package-policies|a1a074bad36e68d54f98d2158d60f879': '10.0.0', diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md index 112491f3a48b9..f30b09fca6212 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/README.md @@ -456,7 +456,7 @@ Once transformed we use an index operation to overwrite the outdated document wi ### Next action -`checkTargetMappings` +`checkTargetTypesMappings` Compare the calculated mappings' hashes against those stored in the `.mappings._meta`. diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.test.ts index b53ace7e6843a..94e5ca0234535 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.test.ts @@ -9,7 +9,7 @@ import * as Either from 'fp-ts/lib/Either'; import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import type { SavedObjectsMappingProperties } from '@kbn/core-saved-objects-server'; -import { checkTargetMappings } from './check_target_mappings'; +import { checkTargetTypesMappings } from './check_target_mappings'; import { getBaseMappings } from '../core'; const indexTypes = ['type1', 'type2', 'type3']; @@ -60,14 +60,14 @@ const appMappings: IndexMapping = { }, }; -describe('checkTargetMappings', () => { +describe('checkTargetTypesMappings', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('when index mappings are missing required properties', () => { it("returns 'index_mappings_incomplete' if index mappings are not defined", async () => { - const task = checkTargetMappings({ + const task = checkTargetTypesMappings({ indexTypes, appMappings, latestMappingsVersions, @@ -78,7 +78,7 @@ describe('checkTargetMappings', () => { }); it("returns 'index_mappings_incomplete' if index mappings do not define _meta", async () => { - const task = checkTargetMappings({ + const task = checkTargetTypesMappings({ indexTypes, appMappings, indexMappings: { @@ -93,7 +93,7 @@ describe('checkTargetMappings', () => { }); it("returns 'index_mappings_incomplete' if index mappings do not define migrationMappingPropertyHashes nor mappingVersions", async () => { - const task = checkTargetMappings({ + const task = checkTargetTypesMappings({ indexTypes, appMappings, indexMappings: { @@ -109,7 +109,7 @@ describe('checkTargetMappings', () => { }); it("returns 'index_mappings_incomplete' if index mappings define a different value for 'dynamic' property", async () => { - const task = checkTargetMappings({ + const task = checkTargetTypesMappings({ indexTypes, appMappings, indexMappings: { @@ -128,7 +128,7 @@ describe('checkTargetMappings', () => { describe('when index mappings have all required properties', () => { describe('when some core properties (aka root fields) have changed', () => { it('returns the list of fields that have changed', async () => { - const task = checkTargetMappings({ + const task = checkTargetTypesMappings({ indexTypes, appMappings, indexMappings: { @@ -149,8 +149,9 @@ describe('checkTargetMappings', () => { const result = await task(); expect(result).toEqual( Either.left({ - type: 'root_fields_changed' as const, - updatedFields: ['references'], + type: 'types_changed' as const, + // types are flagged as changed cause we have not provided a hashToVersionMap + updatedTypes: ['type1', 'type2'], }) ); }); @@ -159,8 +160,8 @@ describe('checkTargetMappings', () => { describe('when core properties have NOT changed', () => { describe('when index mappings ONLY contain the legacy hashes', () => { describe('and legacy hashes match the current model versions', () => { - it('returns a compared_mappings_match response', async () => { - const task = checkTargetMappings({ + it('returns a types_match response', async () => { + const task = checkTargetTypesMappings({ indexTypes, appMappings, indexMappings: legacyMappings, @@ -175,7 +176,7 @@ describe('checkTargetMappings', () => { const result = await task(); expect(result).toEqual( Either.right({ - type: 'compared_mappings_match' as const, + type: 'types_match' as const, }) ); }); @@ -183,7 +184,7 @@ describe('checkTargetMappings', () => { describe('and legacy hashes do NOT match the current model versions', () => { it('returns the list of updated SO types', async () => { - const task = checkTargetMappings({ + const task = checkTargetTypesMappings({ indexTypes, appMappings, indexMappings: legacyMappings, @@ -207,8 +208,8 @@ describe('checkTargetMappings', () => { describe('when index mappings contain the mappingVersions', () => { describe('and mappingVersions match', () => { - it('returns a compared_mappings_match response', async () => { - const task = checkTargetMappings({ + it('returns a types_match response', async () => { + const task = checkTargetTypesMappings({ indexTypes, appMappings, indexMappings: { @@ -228,7 +229,7 @@ describe('checkTargetMappings', () => { const result = await task(); expect(result).toEqual( Either.right({ - type: 'compared_mappings_match' as const, + type: 'types_match' as const, }) ); }); @@ -236,7 +237,7 @@ describe('checkTargetMappings', () => { describe('and mappingVersions do NOT match', () => { it('returns the list of updated SO types', async () => { - const task = checkTargetMappings({ + const task = checkTargetTypesMappings({ indexTypes, appMappings, indexMappings: { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.ts index ac9f047a081e5..029989d89935b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_target_mappings.ts @@ -9,10 +9,10 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import type { IndexMapping, VirtualVersionMap } from '@kbn/core-saved-objects-base-server-internal'; -import { getUpdatedRootFields, getUpdatedTypes } from '../core/compare_mappings'; +import { getUpdatedTypes } from '../core/compare_mappings'; /** @internal */ -export interface CheckTargetMappingsParams { +export interface CheckTargetTypesMappingsParams { indexTypes: string[]; indexMappings?: IndexMapping; appMappings: IndexMapping; @@ -21,34 +21,31 @@ export interface CheckTargetMappingsParams { } /** @internal */ -export interface ComparedMappingsMatch { - type: 'compared_mappings_match'; -} - export interface IndexMappingsIncomplete { type: 'index_mappings_incomplete'; } -export interface RootFieldsChanged { - type: 'root_fields_changed'; - updatedFields: string[]; +/** @internal */ +export interface TypesMatch { + type: 'types_match'; } +/** @internal */ export interface TypesChanged { type: 'types_changed'; updatedTypes: string[]; } -export const checkTargetMappings = +export const checkTargetTypesMappings = ({ indexTypes, indexMappings, appMappings, latestMappingsVersions, hashToVersionMap = {}, - }: CheckTargetMappingsParams): TaskEither.TaskEither< - IndexMappingsIncomplete | RootFieldsChanged | TypesChanged, - ComparedMappingsMatch + }: CheckTargetTypesMappingsParams): TaskEither.TaskEither< + IndexMappingsIncomplete | TypesChanged, + TypesMatch > => async () => { if ( @@ -59,15 +56,6 @@ export const checkTargetMappings = return Either.left({ type: 'index_mappings_incomplete' as const }); } - const updatedFields = getUpdatedRootFields(indexMappings); - - if (updatedFields.length) { - return Either.left({ - type: 'root_fields_changed', - updatedFields, - }); - } - const updatedTypes = getUpdatedTypes({ indexTypes, indexMeta: indexMappings?._meta, @@ -81,6 +69,6 @@ export const checkTargetMappings = updatedTypes, }); } else { - return Either.right({ type: 'compared_mappings_match' as const }); + return Either.right({ type: 'types_match' as const }); } }; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts index afdeb6628a1ba..79df66c4b9ccb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts @@ -87,7 +87,7 @@ export { synchronizeMigrators } from './synchronize_migrators'; export { createIndex } from './create_index'; -export { checkTargetMappings } from './check_target_mappings'; +export { checkTargetTypesMappings } from './check_target_mappings'; export const noop = async (): Promise> => right('noop' as const); @@ -108,11 +108,7 @@ import type { UnknownDocsFound } from './check_for_unknown_docs'; import type { IncompatibleClusterRoutingAllocation } from './initialize_action'; import type { ClusterShardLimitExceeded } from './create_index'; import type { SynchronizationFailed } from './synchronize_migrators'; -import type { - IndexMappingsIncomplete, - RootFieldsChanged, - TypesChanged, -} from './check_target_mappings'; +import type { IndexMappingsIncomplete, TypesChanged } from './check_target_mappings'; export type { CheckForUnknownDocsParams, @@ -187,7 +183,6 @@ export interface ActionErrorTypeMap { es_response_too_large: EsResponseTooLargeError; synchronization_failed: SynchronizationFailed; index_mappings_incomplete: IndexMappingsIncomplete; - root_fields_changed: RootFieldsChanged; types_changed: TypesChanged; operation_not_supported: OperationNotSupported; } diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/diff_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/diff_mappings.ts index b7eeaaf40f079..788ad9e282b6e 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/diff_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/diff_mappings.ts @@ -10,10 +10,18 @@ import type { IndexMapping, VirtualVersionMap } from '@kbn/core-saved-objects-ba import { getUpdatedRootFields, getUpdatedTypes } from './compare_mappings'; /** - * Diffs the actual vs expected mappings. The properties are compared using md5 hashes stored in _meta, because - * actual and expected mappings *can* differ, but if the md5 hashes stored in actual._meta.migrationMappingPropertyHashes - * match our expectations, we don't require a migration. This allows ES to tack on additional mappings that Kibana - * doesn't know about or expect, without triggering continual migrations. + * Diffs the stored vs app mappings. + * On one hand, it compares changes in root fields, by deep comparing the actual mappings. + * On the other hand, it compares changes in SO types mappings: + * Historically, this comparison was done using md5 hashes. + * Currently, and in order to be FIPS compliant, this has been replaced by comparing model versions. + * The `getUpdatedTypes` uses a map to handle the transition md5 => modelVersion + + * @param indexMappings The mappings stored in the SO index + * @param appMappings The current Kibana mappings, computed from the typeRegistry + * @param indexTypes A list of the SO types that are bound to the SO index + * @param latestMappingsVersions A map containing the latest version in which each type has updated its mappings + * @param hashToVersionMap Map that holds md5 => modelVersion equivalence, to smoothly transition away from hashes */ export function diffMappings({ indexMappings, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts index 5dba20c7826a8..f06e9f836f2c5 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts @@ -19,7 +19,7 @@ import type { BaseState, CalculateExcludeFiltersState, UpdateSourceMappingsPropertiesState, - CheckTargetMappingsState, + CheckTargetTypesMappingsState, CheckUnknownDocumentsState, CheckVersionIndexReadyActions, CleanupUnknownAndExcluded, @@ -2592,7 +2592,7 @@ describe('migrations v2 model', () => { it('OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT -> CHECK_TARGET_MAPPINGS if action succeeded', () => { const res: ResponseType<'OUTDATED_DOCUMENTS_SEARCH_CLOSE_PIT'> = Either.right({}); - const newState = model(state, res) as CheckTargetMappingsState; + const newState = model(state, res) as CheckTargetTypesMappingsState; expect(newState.controlState).toBe('CHECK_TARGET_MAPPINGS'); // @ts-expect-error pitId shouldn't leak outside expect(newState.pitId).toBe(undefined); @@ -2600,7 +2600,7 @@ describe('migrations v2 model', () => { }); describe('CHECK_TARGET_MAPPINGS', () => { - const checkTargetMappingsState: CheckTargetMappingsState = { + const checkTargetTypesMappingsState: CheckTargetTypesMappingsState = { ...postInitState, controlState: 'CHECK_TARGET_MAPPINGS', versionIndexReadyActions: Option.none, @@ -2614,7 +2614,7 @@ describe('migrations v2 model', () => { type: 'index_mappings_incomplete' as const, }); const newState = model( - checkTargetMappingsState, + checkTargetTypesMappingsState, res ) as UpdateTargetMappingsPropertiesState; expect(newState.controlState).toBe('UPDATE_TARGET_MAPPINGS_PROPERTIES'); @@ -2623,28 +2623,14 @@ describe('migrations v2 model', () => { }); describe('compatible migration', () => { - it('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_PROPERTIES if core fields have been updated', () => { - const res: ResponseType<'CHECK_TARGET_MAPPINGS'> = Either.left({ - type: 'root_fields_changed' as const, - updatedFields: ['references'], - }); - const newState = model( - checkTargetMappingsState, - res - ) as UpdateTargetMappingsPropertiesState; - expect(newState.controlState).toBe('UPDATE_TARGET_MAPPINGS_PROPERTIES'); - // since a core field has been updated, we must pickup ALL SOs. - // Thus, we must NOT define a filter query. - expect(Option.isNone(newState.updatedTypesQuery)).toEqual(true); - }); - - it('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_PROPERTIES if only SO types have changed', () => { + it('CHECK_TARGET_MAPPINGS -> UPDATE_TARGET_MAPPINGS_PROPERTIES if SO types have changed', () => { const res: ResponseType<'CHECK_TARGET_MAPPINGS'> = Either.left({ type: 'types_changed' as const, + updatedFields: [], updatedTypes: ['dashboard', 'lens'], }); const newState = model( - checkTargetMappingsState, + checkTargetTypesMappingsState, res ) as UpdateTargetMappingsPropertiesState; expect(newState.controlState).toBe('UPDATE_TARGET_MAPPINGS_PROPERTIES'); @@ -2668,11 +2654,14 @@ describe('migrations v2 model', () => { }); }); - it('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS if mappings match', () => { + it('CHECK_TARGET_MAPPINGS -> CHECK_VERSION_INDEX_READY_ACTIONS if types match (there might be additions in core fields)', () => { const res: ResponseType<'CHECK_TARGET_MAPPINGS'> = Either.right({ - type: 'compared_mappings_match' as const, + type: 'types_match' as const, }); - const newState = model(checkTargetMappingsState, res) as CheckVersionIndexReadyActions; + const newState = model( + checkTargetTypesMappingsState, + res + ) as CheckVersionIndexReadyActions; expect(newState.controlState).toBe('CHECK_VERSION_INDEX_READY_ACTIONS'); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts index b232a7d750925..e25d48af4ed25 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts @@ -1429,40 +1429,33 @@ export const model = (currentState: State, resW: ResponseType): } else if (stateP.controlState === 'CHECK_TARGET_MAPPINGS') { const res = resW as ResponseType; if (Either.isRight(res)) { - // The mappings have NOT changed, no need to pick up changes in any documents + // The types mappings have NOT changed, no need to pick up changes in any documents return { ...stateP, controlState: 'CHECK_VERSION_INDEX_READY_ACTIONS', + logs: [ + ...stateP.logs, + { + level: 'info', + message: + 'There are no changes in the mappings of any of the SO types, skipping UPDATE_TARGET_MAPPINGS steps.', + }, + ], }; } else { const left = res.left; if (isTypeof(left, 'index_mappings_incomplete')) { // reindex migration - // some top-level properties have changed, e.g. 'dynamic' or '_meta' (see checkTargetMappings()) + // some top-level properties have changed, e.g. 'dynamic' or '_meta' (see checkTargetTypesMappings()) // we must "pick-up" all documents on the index (by not providing a query) return { ...stateP, controlState: 'UPDATE_TARGET_MAPPINGS_PROPERTIES', updatedTypesQuery: Option.none, }; - } else if (isTypeof(left, 'root_fields_changed')) { - // compatible migration: some core fields have been updated - return { - ...stateP, - controlState: 'UPDATE_TARGET_MAPPINGS_PROPERTIES', - // we must "pick-up" all documents on the index (by not providing a query) - updatedTypesQuery: Option.none, - logs: [ - ...stateP.logs, - { - level: 'info', - message: `Kibana is performing a compatible upgrade and the mappings of some root fields have been changed. For Elasticsearch to pickup these mappings, all saved objects need to be updated. Updated root fields: ${left.updatedFields}.`, - }, - ], - }; } else if (isTypeof(left, 'types_changed')) { - // compatible migration: some fields have been updated, and they all correspond to SO types - const updatedTypesQuery = Option.fromNullable(buildPickupMappingsQuery(left.updatedTypes)); + // compatible migration: the mappings of some SO types have been updated + const updatedTypesQuery = Option.some(buildPickupMappingsQuery(left.updatedTypes)); return { ...stateP, @@ -1473,7 +1466,7 @@ export const model = (currentState: State, resW: ResponseType): ...stateP.logs, { level: 'info', - message: `Kibana is performing a compatible upgrade and NO root fields have been updated. Kibana will update the following SO types so that ES can pickup the updated mappings: ${left.updatedTypes}.`, + message: `Documents of the following SO types will be updated, so that ES can pickup the updated mappings: ${left.updatedTypes}.`, }, ], }; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts index 4df6e59cae22f..9cd7f92ee4355 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts @@ -15,7 +15,7 @@ import type { WaitGroup } from './kibana_migrator_utils'; import type { AllActionStates, CalculateExcludeFiltersState, - CheckTargetMappingsState, + CheckTargetTypesMappingsState, CheckUnknownDocumentsState, CleanupUnknownAndExcluded, CleanupUnknownAndExcludedWaitForTaskState, @@ -203,8 +203,8 @@ export const nextActionMap = ( }), REFRESH_TARGET: (state: RefreshTarget) => Actions.refreshIndex({ client, index: state.targetIndex }), - CHECK_TARGET_MAPPINGS: (state: CheckTargetMappingsState) => - Actions.checkTargetMappings({ + CHECK_TARGET_MAPPINGS: (state: CheckTargetTypesMappingsState) => + Actions.checkTargetTypesMappings({ indexTypes: state.indexTypes, indexMappings: Option.toUndefined(state.sourceIndexMappings), appMappings: state.targetIndexMappings, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts index 2ea6cc9e45708..b26088adc53e9 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts @@ -384,7 +384,7 @@ export interface RefreshTarget extends PostInitState { readonly targetIndex: string; } -export interface CheckTargetMappingsState extends PostInitState { +export interface CheckTargetTypesMappingsState extends PostInitState { readonly controlState: 'CHECK_TARGET_MAPPINGS'; } @@ -549,7 +549,7 @@ export interface LegacyDeleteState extends LegacyBaseState { export type State = Readonly< | CalculateExcludeFiltersState - | CheckTargetMappingsState + | CheckTargetTypesMappingsState | CheckUnknownDocumentsState | CheckVersionIndexReadyActions | CleanupUnknownAndExcluded diff --git a/packages/kbn-apm-synthtrace/src/scenarios/spiked_latency.ts b/packages/kbn-apm-synthtrace/src/scenarios/spiked_latency.ts index 8dfbc6a131843..13eb4dcd3f6d6 100644 --- a/packages/kbn-apm-synthtrace/src/scenarios/spiked_latency.ts +++ b/packages/kbn-apm-synthtrace/src/scenarios/spiked_latency.ts @@ -40,11 +40,10 @@ const scenario: Scenario = async ({ logger }) => { instanceId: generateShortId(), cloudProvider: cluster.provider, cloudRegion: cluster.region, + containerId: `spiked-container-${generateShortId()}`, + hostName: `spiked-host-${generateShortId()}`, })); - const containerId = `spiked-${generateShortId()}`; - const hostName = `spiked-${generateShortId()}`; - function buildLogs(serviceName: string) { return range .interval('1m') @@ -59,6 +58,8 @@ const scenario: Scenario = async ({ logger }) => { instanceId, cloudRegion, cloudProvider, + containerId, + hostName, } = clusters[clusterIndex]; return log @@ -102,6 +103,9 @@ const scenario: Scenario = async ({ logger }) => { const interval = random(1, 100, false); const rangeWithInterval = range.interval(`${interval}s`); + const clusterIndex = Math.floor(Math.random() * clusters.length); + const { containerId, hostName } = clusters[clusterIndex]; + return rangeWithInterval.generator((timestamp, i) => { const duration = getDuration(transactionName); return serviceInstance diff --git a/packages/kbn-check-mappings-update-cli/current_fields.json b/packages/kbn-check-mappings-update-cli/current_fields.json index a66ab72d98aa3..33aace8260291 100644 --- a/packages/kbn-check-mappings-update-cli/current_fields.json +++ b/packages/kbn-check-mappings-update-cli/current_fields.json @@ -471,6 +471,7 @@ ], "infrastructure-ui-source": [], "ingest-agent-policies": [ + "advanced_settings", "agent_features", "agent_features.enabled", "agent_features.name", diff --git a/packages/kbn-check-mappings-update-cli/current_mappings.json b/packages/kbn-check-mappings-update-cli/current_mappings.json index 00d9a5f82fd1c..05e1a48d55d2f 100644 --- a/packages/kbn-check-mappings-update-cli/current_mappings.json +++ b/packages/kbn-check-mappings-update-cli/current_mappings.json @@ -1590,6 +1590,10 @@ }, "ingest-agent-policies": { "properties": { + "advanced_settings": { + "index": false, + "type": "flattened" + }, "agent_features": { "properties": { "enabled": { diff --git a/packages/kbn-config-schema/index.ts b/packages/kbn-config-schema/index.ts index 57c61c125ec73..0aec0ae1f5a1e 100644 --- a/packages/kbn-config-schema/index.ts +++ b/packages/kbn-config-schema/index.ts @@ -238,3 +238,17 @@ export const schema = { }; export type Schema = typeof schema; + +import { + META_FIELD_X_OAS_OPTIONAL, + META_FIELD_X_OAS_MAX_LENGTH, + META_FIELD_X_OAS_MIN_LENGTH, + META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES, +} from './src/oas_meta_fields'; + +export const metaFields = Object.freeze({ + META_FIELD_X_OAS_OPTIONAL, + META_FIELD_X_OAS_MAX_LENGTH, + META_FIELD_X_OAS_MIN_LENGTH, + META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES, +}); diff --git a/packages/kbn-config-schema/src/oas_meta_fields.ts b/packages/kbn-config-schema/src/oas_meta_fields.ts new file mode 100644 index 0000000000000..e54751f36f099 --- /dev/null +++ b/packages/kbn-config-schema/src/oas_meta_fields.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * These fields are used in Joi meta to capture additional information used + * by OpenAPI spec generator. + */ +export const META_FIELD_X_OAS_OPTIONAL = 'x-oas-optional' as const; +export const META_FIELD_X_OAS_MIN_LENGTH = 'x-oas-min-length' as const; +export const META_FIELD_X_OAS_MAX_LENGTH = 'x-oas-max-length' as const; +export const META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES = + 'x-oas-get-additional-properties' as const; diff --git a/packages/kbn-config-schema/src/types/map_of_type.test.ts b/packages/kbn-config-schema/src/types/map_type.test.ts similarity index 94% rename from packages/kbn-config-schema/src/types/map_of_type.test.ts rename to packages/kbn-config-schema/src/types/map_type.test.ts index 7552a6d65a621..f1cb809a16243 100644 --- a/packages/kbn-config-schema/src/types/map_of_type.test.ts +++ b/packages/kbn-config-schema/src/types/map_type.test.ts @@ -7,6 +7,7 @@ */ import { schema } from '../..'; +import { META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES } from '../oas_meta_fields'; test('handles object as input', () => { const type = schema.mapOf(schema.string(), schema.string()); @@ -186,6 +187,17 @@ test('error preserves full path', () => { ); }); +test('meta', () => { + const stringSchema = schema.string(); + const type = schema.mapOf(schema.string(), stringSchema); + const result = type + .getSchema() + .describe() + .metas![0][META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES](); + + expect(result).toBe(stringSchema.getSchema()); +}); + describe('#extendsDeep', () => { describe('#keyType', () => { const type = schema.mapOf(schema.string(), schema.object({ foo: schema.string() })); diff --git a/packages/kbn-config-schema/src/types/map_type.ts b/packages/kbn-config-schema/src/types/map_type.ts index 61caf76efc613..cf14d7af7f1cb 100644 --- a/packages/kbn-config-schema/src/types/map_type.ts +++ b/packages/kbn-config-schema/src/types/map_type.ts @@ -9,6 +9,7 @@ import typeDetect from 'type-detect'; import { SchemaTypeError, SchemaTypesError } from '../errors'; import { internals } from '../internals'; +import { META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES } from '../oas_meta_fields'; import { Type, TypeOptions, ExtendsDeepOptions } from './type'; export type MapOfOptions = TypeOptions>; @@ -20,7 +21,12 @@ export class MapOfType extends Type> { constructor(keyType: Type, valueType: Type, options: MapOfOptions = {}) { const defaultValue = options.defaultValue; - const schema = internals.map().entries(keyType.getSchema(), valueType.getSchema()); + const schema = internals + .map() + .entries(keyType.getSchema(), valueType.getSchema()) + .meta({ + [META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES]: () => valueType.getSchema(), + }); super(schema, { ...options, diff --git a/packages/kbn-config-schema/src/types/maybe_type.test.ts b/packages/kbn-config-schema/src/types/maybe_type.test.ts index ff69c2bb36045..312b88520a12a 100644 --- a/packages/kbn-config-schema/src/types/maybe_type.test.ts +++ b/packages/kbn-config-schema/src/types/maybe_type.test.ts @@ -7,6 +7,7 @@ */ import { schema } from '../..'; +import { META_FIELD_X_OAS_OPTIONAL } from '../oas_meta_fields'; test('returns value if specified', () => { const type = schema.maybe(schema.string()); @@ -96,6 +97,12 @@ describe('maybe + object', () => { }); }); +test('meta', () => { + const maybeString = schema.maybe(schema.string()); + const result = maybeString.getSchema().describe().metas[0]; + expect(result).toEqual({ [META_FIELD_X_OAS_OPTIONAL]: true }); +}); + describe('#extendsDeep', () => { const type = schema.maybe(schema.object({ foo: schema.string() })); diff --git a/packages/kbn-config-schema/src/types/maybe_type.ts b/packages/kbn-config-schema/src/types/maybe_type.ts index e90434077cc36..6cd88dfc9a7cd 100644 --- a/packages/kbn-config-schema/src/types/maybe_type.ts +++ b/packages/kbn-config-schema/src/types/maybe_type.ts @@ -8,6 +8,7 @@ import { Type, ExtendsDeepOptions } from './type'; +import { META_FIELD_X_OAS_OPTIONAL } from '../oas_meta_fields'; export class MaybeType extends Type { private readonly maybeType: Type; @@ -16,6 +17,7 @@ export class MaybeType extends Type { type .getSchema() .optional() + .meta({ [META_FIELD_X_OAS_OPTIONAL]: true }) .default(() => undefined) ); this.maybeType = type; diff --git a/packages/kbn-config-schema/src/types/record_type.test.ts b/packages/kbn-config-schema/src/types/record_type.test.ts index 9bfd4fab31b7a..609c17eb43d8e 100644 --- a/packages/kbn-config-schema/src/types/record_type.test.ts +++ b/packages/kbn-config-schema/src/types/record_type.test.ts @@ -7,6 +7,7 @@ */ import { schema } from '../..'; +import { META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES } from '../oas_meta_fields'; test('handles object as input', () => { const type = schema.recordOf(schema.string(), schema.string()); @@ -208,3 +209,14 @@ describe('#extendsDeep', () => { ).toThrowErrorMatchingInlineSnapshot(`"[key.bar]: definition for this key is missing"`); }); }); + +test('meta', () => { + const stringSchema = schema.string(); + const type = schema.mapOf(schema.string(), stringSchema); + const result = type + .getSchema() + .describe() + .metas![0][META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES](); + + expect(result).toBe(stringSchema.getSchema()); +}); diff --git a/packages/kbn-config-schema/src/types/record_type.ts b/packages/kbn-config-schema/src/types/record_type.ts index 782263d9932ea..ac083ada116bc 100644 --- a/packages/kbn-config-schema/src/types/record_type.ts +++ b/packages/kbn-config-schema/src/types/record_type.ts @@ -10,6 +10,7 @@ import typeDetect from 'type-detect'; import { SchemaTypeError, SchemaTypesError } from '../errors'; import { internals } from '../internals'; import { Type, TypeOptions, ExtendsDeepOptions } from './type'; +import { META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES } from '../oas_meta_fields'; export type RecordOfOptions = TypeOptions>; @@ -19,7 +20,12 @@ export class RecordOfType extends Type> { private readonly options: RecordOfOptions; constructor(keyType: Type, valueType: Type, options: RecordOfOptions = {}) { - const schema = internals.record().entries(keyType.getSchema(), valueType.getSchema()); + const schema = internals + .record() + .entries(keyType.getSchema(), valueType.getSchema()) + .meta({ + [META_FIELD_X_OAS_GET_ADDITIONAL_PROPERTIES]: () => valueType.getSchema(), + }); super(schema, options); this.keyType = keyType; diff --git a/packages/kbn-config-schema/src/types/string_type.test.ts b/packages/kbn-config-schema/src/types/string_type.test.ts index d07b52983d88c..0f567cd2b7e20 100644 --- a/packages/kbn-config-schema/src/types/string_type.test.ts +++ b/packages/kbn-config-schema/src/types/string_type.test.ts @@ -7,6 +7,7 @@ */ import { schema } from '../..'; +import { META_FIELD_X_OAS_MAX_LENGTH, META_FIELD_X_OAS_MIN_LENGTH } from '../oas_meta_fields'; test('returns value is string and defined', () => { expect(schema.string().validate('test')).toBe('test'); @@ -166,6 +167,17 @@ describe('#defaultValue', () => { }); }); +test('meta', () => { + const string = schema.string({ minLength: 1, maxLength: 3 }); + const [meta1, meta2] = string.getSchema().describe().metas; + expect(meta1).toEqual({ + [META_FIELD_X_OAS_MIN_LENGTH]: 1, + }); + expect(meta2).toEqual({ + [META_FIELD_X_OAS_MAX_LENGTH]: 3, + }); +}); + describe('#validate', () => { test('is called with input value', () => { let calledWith; diff --git a/packages/kbn-config-schema/src/types/string_type.ts b/packages/kbn-config-schema/src/types/string_type.ts index e5251013e3a7e..999a4d85fb384 100644 --- a/packages/kbn-config-schema/src/types/string_type.ts +++ b/packages/kbn-config-schema/src/types/string_type.ts @@ -10,6 +10,8 @@ import typeDetect from 'type-detect'; import { internals } from '../internals'; import { Type, TypeOptions, convertValidationFunction } from './type'; +import { META_FIELD_X_OAS_MIN_LENGTH, META_FIELD_X_OAS_MAX_LENGTH } from '../oas_meta_fields'; + export type StringOptions = TypeOptions & { minLength?: number; maxLength?: number; @@ -37,24 +39,29 @@ export class StringType extends Type { } return value; }); + if (options.minLength !== undefined) { - schema = schema.custom( - convertValidationFunction((value) => { - if (value.length < options.minLength!) { - return `value has length [${value.length}] but it must have a minimum length of [${options.minLength}].`; - } - }) - ); + schema = schema + .custom( + convertValidationFunction((value) => { + if (value.length < options.minLength!) { + return `value has length [${value.length}] but it must have a minimum length of [${options.minLength}].`; + } + }) + ) + .meta({ [META_FIELD_X_OAS_MIN_LENGTH]: options.minLength }); } if (options.maxLength !== undefined) { - schema = schema.custom( - convertValidationFunction((value) => { - if (value.length > options.maxLength!) { - return `value has length [${value.length}] but it must have a maximum length of [${options.maxLength}].`; - } - }) - ); + schema = schema + .custom( + convertValidationFunction((value) => { + if (value.length > options.maxLength!) { + return `value has length [${value.length}] but it must have a maximum length of [${options.maxLength}].`; + } + }) + ) + .meta({ [META_FIELD_X_OAS_MAX_LENGTH]: options.maxLength }); } schema.type = 'string'; diff --git a/packages/kbn-config-schema/src/types/type.test.ts b/packages/kbn-config-schema/src/types/type.test.ts new file mode 100644 index 0000000000000..9cb61b88aa030 --- /dev/null +++ b/packages/kbn-config-schema/src/types/type.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { get } from 'lodash'; +import { internals } from '../internals'; +import { Type, TypeOptions } from './type'; + +class MyType extends Type { + constructor(opts: TypeOptions = {}) { + super(internals.any(), opts); + } +} + +test('describe', () => { + const type = new MyType({ description: 'my description' }); + const meta = type.getSchema().describe(); + expect(get(meta, 'flags.description')).toBe('my description'); +}); diff --git a/packages/kbn-config-schema/src/types/type.ts b/packages/kbn-config-schema/src/types/type.ts index 8023da2dda920..145650d0e3d82 100644 --- a/packages/kbn-config-schema/src/types/type.ts +++ b/packages/kbn-config-schema/src/types/type.ts @@ -13,6 +13,8 @@ import { Reference } from '../references'; export interface TypeOptions { defaultValue?: T | Reference | (() => T); validate?: (value: T) => string | void; + /** A human-friendly description of this type to be used in documentation */ + description?: string; } export interface SchemaStructureEntry { @@ -86,6 +88,10 @@ export abstract class Type { schema = schema.custom(convertValidationFunction(options.validate)); } + if (options.description) { + schema = schema.description(options.description); + } + // Attach generic error handler only if it hasn't been attached yet since // only the last error handler is counted. if (schema.$_getFlag('error') === undefined) { diff --git a/packages/kbn-es-types/src/search.ts b/packages/kbn-es-types/src/search.ts index 43fbe15586090..f9f304914afaa 100644 --- a/packages/kbn-es-types/src/search.ts +++ b/packages/kbn-es-types/src/search.ts @@ -678,6 +678,7 @@ export interface ESQLSearchParams { // https://github.com/elastic/elasticsearch/pull/102767 // time_zone?: string; query: string; + version: string; filter?: unknown; locale?: string; dropNullColumns?: boolean; diff --git a/packages/kbn-esql-utils/constants.ts b/packages/kbn-esql-utils/constants.ts new file mode 100644 index 0000000000000..51dcbab83654b --- /dev/null +++ b/packages/kbn-esql-utils/constants.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// we are expecting to retrieve this from an API instead +// https://github.com/elastic/elasticsearch/issues/107069 +export const ESQL_LATEST_VERSION = '2024.04.01'; diff --git a/packages/kbn-esql-utils/index.ts b/packages/kbn-esql-utils/index.ts index 1592777d136f3..885c491986582 100644 --- a/packages/kbn-esql-utils/index.ts +++ b/packages/kbn-esql-utils/index.ts @@ -17,3 +17,5 @@ export { getESQLWithSafeLimit, TextBasedLanguages, } from './src'; + +export { ESQL_LATEST_VERSION } from './constants'; diff --git a/packages/kbn-generate-csv/src/generate_csv_esql.test.ts b/packages/kbn-generate-csv/src/generate_csv_esql.test.ts index 3e925893d37e4..ee514788c72cf 100644 --- a/packages/kbn-generate-csv/src/generate_csv_esql.test.ts +++ b/packages/kbn-generate-csv/src/generate_csv_esql.test.ts @@ -22,6 +22,7 @@ import { IKibanaSearchResponse } from '@kbn/data-plugin/common'; import { IScopedSearchClient } from '@kbn/data-plugin/server'; import { dataPluginMock } from '@kbn/data-plugin/server/mocks'; import { CancellationToken } from '@kbn/reporting-common'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import type { ReportingConfigType } from '@kbn/reporting-server'; import type { ESQLSearchReponse as ESQLSearchResponse } from '@kbn/es-types'; import { @@ -311,7 +312,7 @@ describe('CsvESQLGenerator', () => { ); expect(mockDataClientSearchFn).toBeCalledWith( - { params: { filter: undefined, locale: 'en', query: '' } }, + { params: { filter: undefined, locale: 'en', query: '', version: ESQL_LATEST_VERSION } }, { strategy: 'esql', transport: { @@ -389,7 +390,7 @@ describe('CsvESQLGenerator', () => { ); expect(mockDataClientSearchFn).toBeCalledWith( - { params: { filter: undefined, locale: 'en', query: '' } }, + { params: { filter: undefined, locale: 'en', query: '', version: ESQL_LATEST_VERSION } }, { strategy: 'esql', transport: { @@ -485,6 +486,7 @@ describe('CsvESQLGenerator', () => { }, locale: 'en', query: '', + version: ESQL_LATEST_VERSION, }, }, { diff --git a/packages/kbn-generate-csv/src/generate_csv_esql.ts b/packages/kbn-generate-csv/src/generate_csv_esql.ts index cea0838460ab5..dbfe073ff62f3 100644 --- a/packages/kbn-generate-csv/src/generate_csv_esql.ts +++ b/packages/kbn-generate-csv/src/generate_csv_esql.ts @@ -8,7 +8,7 @@ import { lastValueFrom } from 'rxjs'; import type { Writable } from 'stream'; - +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import { errors as esErrors } from '@elastic/elasticsearch'; import type { IScopedClusterClient, IUiSettingsClient, Logger } from '@kbn/core/server'; import { @@ -100,6 +100,7 @@ export class CsvESQLGenerator { // we will need to add it back in once it is supported again. // https://github.com/elastic/elasticsearch/pull/102767 // timezone + version: ESQL_LATEST_VERSION, }, }; diff --git a/packages/kbn-generate-csv/tsconfig.json b/packages/kbn-generate-csv/tsconfig.json index 6db6b731929a1..ce90e0fc346d5 100644 --- a/packages/kbn-generate-csv/tsconfig.json +++ b/packages/kbn-generate-csv/tsconfig.json @@ -28,5 +28,6 @@ "@kbn/reporting-export-types-csv-common", "@kbn/es-query", "@kbn/es-types", + "@kbn/esql-utils", ] } diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index d36895151587f..ecf2ea603767d 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -20,6 +20,7 @@ import * as UiSharedDepsSrc from '@kbn/ui-shared-deps-src'; import StatoscopeWebpackPlugin from '@statoscope/webpack-plugin'; // @ts-expect-error import VisualizerPlugin from 'webpack-visualizer-plugin2'; +import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import { Bundle, BundleRemotes, WorkerConfig, parseDllManifest } from '../common'; import { BundleRemotesPlugin } from './bundle_remotes_plugin'; @@ -92,6 +93,12 @@ export function getWebpackConfig( saveReportTo: `${bundle.outputDir}/${bundle.id}.statoscope.html`, }), new VisualizerPlugin({ filename: `${bundle.id}.visualizer.html` }), + new BundleAnalyzerPlugin({ + analyzerMode: 'static', + reportFilename: `${bundle.id}.analyzer.html`, + openAnalyzer: false, + logLevel: 'silent', + }), ] : []), // @ts-ignore something is wrong with the StatoscopeWebpackPlugin type. diff --git a/packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts b/packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts index 347bdba723808..7d63c02633e77 100644 --- a/packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts +++ b/packages/kbn-unified-field-list/src/services/field_stats_text_based/load_field_stats_text_based.ts @@ -15,7 +15,7 @@ import type { } from '@kbn/data-plugin/public'; import type { ESQLSearchParams, ESQLSearchReponse } from '@kbn/es-types'; import type { AggregateQuery } from '@kbn/es-query'; -import { getESQLWithSafeLimit } from '@kbn/esql-utils'; +import { ESQL_LATEST_VERSION, getESQLWithSafeLimit } from '@kbn/esql-utils'; import type { FieldStatsResponse } from '../../types'; import { buildSearchFilter, @@ -77,6 +77,7 @@ export const loadFieldStatsTextBased: LoadFieldStatsTextBasedHandler = async ({ params: { ...(filter ? { filter } : {}), ...body, + version: ESQL_LATEST_VERSION, }, }, { diff --git a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts index 9a1299b6f1fe4..d2d3761c7ab31 100644 --- a/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts +++ b/src/core/server/integration_tests/ci_checks/saved_objects/check_registered_types.test.ts @@ -106,7 +106,7 @@ describe('checking migration metadata changes on all registered SO types', () => "infra-custom-dashboards": "1a5994f2e05bb8a1609825ddbf5012f77c5c67f3", "infrastructure-monitoring-log-view": "5f86709d3c27aed7a8379153b08ee5d3d90d77f5", "infrastructure-ui-source": "113182d6895764378dfe7fa9fa027244f3a457c4", - "ingest-agent-policies": "7633e578f60c074f8267bc50ec4763845e431437", + "ingest-agent-policies": "d2ee0bf36a512c2ac744b0def1c822b7880f1f83", "ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d", "ingest-outputs": "daafff49255ab700e07491376fe89f04fc998b91", "ingest-package-policies": "8a99e165aab00c6c365540427a3abeb7bea03f31", diff --git a/src/core/server/integration_tests/saved_objects/migrations/group4/v2_md5_to_mv.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group4/v2_md5_to_mv.test.ts index 1304044b63eb5..4e1cb4d06e38f 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group4/v2_md5_to_mv.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group4/v2_md5_to_mv.test.ts @@ -184,7 +184,7 @@ describe('V2 algorithm', () => { it('only "picks up" the types that have changed', async () => { const logs = await readLog(logFilePath); expect(logs).toMatch( - 'Kibana is performing a compatible upgrade and NO root fields have been updated. Kibana will update the following SO types so that ES can pickup the updated mappings: another-type.' + 'Documents of the following SO types will be updated, so that ES can pickup the updated mappings: another-type.' ); }); }); @@ -263,7 +263,7 @@ describe('V2 algorithm', () => { it('only "picks up" the types that have changed', async () => { const logs = await readLog(logFilePath); expect(logs).toMatch( - 'Kibana is performing a compatible upgrade and NO root fields have been updated. Kibana will update the following SO types so that ES can pickup the updated mappings: another-type.' + 'Documents of the following SO types will be updated, so that ES can pickup the updated mappings: another-type.' ); }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/group5/pickup_updated_types_only.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group5/pickup_updated_types_only.test.ts index bb216b401e885..91fed904d01d7 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group5/pickup_updated_types_only.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group5/pickup_updated_types_only.test.ts @@ -12,8 +12,8 @@ import { clearLog, createBaseline, defaultKibanaIndex, - defaultLogFilePath, getCompatibleMappingsMigrator, + getIdenticalMappingsMigrator, getIncompatibleMappingsMigrator, startElasticsearch, } from '../kibana_migrator_test_kit'; @@ -31,38 +31,39 @@ describe('pickupUpdatedMappings', () => { beforeEach(async () => { await createBaseline(); - await clearLog(); + await clearLog(logFilePath); }); describe('when performing a reindexing migration', () => { it('should pickup all documents from the index', async () => { - const { runMigrations } = await getIncompatibleMappingsMigrator(); + const { runMigrations } = await getIncompatibleMappingsMigrator({ logFilePath }); await runMigrations(); - const logs = await parseLogFile(defaultLogFilePath); + const logs = await parseLogFile(logFilePath); + expect(logs).not.toContainLogEntry('Documents of the following SO types will be updated'); expect(logs).not.toContainLogEntry( - 'Kibana is performing a compatible upgrade and NO root fields have been updated. Kibana will update the following SO types so that ES can pickup the updated mappings' + 'There are no changes in the mappings of any of the SO types, skipping UPDATE_TARGET_MAPPINGS steps.' ); }); }); describe('when performing a compatible migration', () => { it('should pickup only the types that have been updated', async () => { - const { runMigrations } = await getCompatibleMappingsMigrator(); + const { runMigrations } = await getCompatibleMappingsMigrator({ logFilePath }); await runMigrations(); - const logs = await parseLogFile(defaultLogFilePath); + const logs = await parseLogFile(logFilePath); expect(logs).toContainLogEntry( - 'Kibana is performing a compatible upgrade and NO root fields have been updated. Kibana will update the following SO types so that ES can pickup the updated mappings: complex.' + 'Documents of the following SO types will be updated, so that ES can pickup the updated mappings: complex.' ); }); - it('should pickup ALL documents if any root fields have been updated', async () => { - const { runMigrations, client } = await getCompatibleMappingsMigrator(); + it('should NOT pickup any documents if only root fields have been updated', async () => { + const { runMigrations, client } = await getIdenticalMappingsMigrator({ logFilePath }); // we tamper the baseline mappings to simulate some root fields changes const baselineMappings = await client.indices.getMapping({ index: defaultKibanaIndex }); @@ -74,14 +75,12 @@ describe('pickupUpdatedMappings', () => { await runMigrations(); - const logs = await parseLogFile(defaultLogFilePath); + const logs = await parseLogFile(logFilePath); expect(logs).toContainLogEntry( - 'Kibana is performing a compatible upgrade and the mappings of some root fields have been changed. For Elasticsearch to pickup these mappings, all saved objects need to be updated. Updated root fields: references.' - ); - expect(logs).not.toContainLogEntry( - 'Kibana is performing a compatible upgrade and NO root fields have been updated. Kibana will update the following SO types so that ES can pickup the updated mappings' + 'There are no changes in the mappings of any of the SO types, skipping UPDATE_TARGET_MAPPINGS steps.' ); + expect(logs).not.toContainLogEntry('Documents of the following SO types will be updated'); }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts index d748cfd16755b..db89ae88068fb 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/kibana_migrator_test_kit.ts @@ -434,15 +434,18 @@ export const createBaseline = async () => { }; interface GetMutatedMigratorParams { + logFilePath?: string; kibanaVersion?: string; settings?: Record; } export const getIdenticalMappingsMigrator = async ({ + logFilePath = defaultLogFilePath, kibanaVersion = nextMinor, settings = {}, }: GetMutatedMigratorParams = {}) => { return await getKibanaMigratorTestKit({ + logFilePath, types: baselineTypes, kibanaVersion, settings, @@ -450,10 +453,12 @@ export const getIdenticalMappingsMigrator = async ({ }; export const getNonDeprecatedMappingsMigrator = async ({ + logFilePath = defaultLogFilePath, kibanaVersion = nextMinor, settings = {}, }: GetMutatedMigratorParams = {}) => { return await getKibanaMigratorTestKit({ + logFilePath, types: baselineTypes.filter((type) => type.name !== 'deprecated'), kibanaVersion, settings, @@ -461,6 +466,7 @@ export const getNonDeprecatedMappingsMigrator = async ({ }; export const getCompatibleMappingsMigrator = async ({ + logFilePath = defaultLogFilePath, filterDeprecated = false, kibanaVersion = nextMinor, settings = {}, @@ -499,6 +505,7 @@ export const getCompatibleMappingsMigrator = async ({ }); return await getKibanaMigratorTestKit({ + logFilePath, types, kibanaVersion, settings, @@ -506,6 +513,7 @@ export const getCompatibleMappingsMigrator = async ({ }; export const getIncompatibleMappingsMigrator = async ({ + logFilePath = defaultLogFilePath, kibanaVersion = nextMinor, settings = {}, }: GetMutatedMigratorParams = {}) => { @@ -544,6 +552,7 @@ export const getIncompatibleMappingsMigrator = async ({ }); return await getKibanaMigratorTestKit({ + logFilePath, types, kibanaVersion, settings, diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx index ca21711d9bfb7..5ed6fdf52f669 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx @@ -11,7 +11,7 @@ import { ColorStop } from '@kbn/coloring'; import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; import type { Datatable } from '@kbn/expressions-plugin/public'; -import { DatatableColumn, DatatableRow } from '@kbn/expressions-plugin/common'; +import { DatatableColumn, DatatableColumnMeta, DatatableRow } from '@kbn/expressions-plugin/common'; import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { GaugeRenderProps, @@ -29,15 +29,7 @@ import { ColorBandSimpleConfig, Color, } from '@elastic/charts'; - -jest.mock('@elastic/charts', () => { - const original = jest.requireActual('@elastic/charts'); - - return { - ...original, - getSpecId: jest.fn(() => {}), - }; -}); +import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common'; const numberColumn = (id = 'metric-accessor'): DatatableColumn => ({ id, @@ -51,17 +43,8 @@ const numberColumn = (id = 'metric-accessor'): DatatableColumn => ({ }, }); -jest.mock('@elastic/charts', () => { - const original = jest.requireActual('@elastic/charts'); - - return { - ...original, - getSpecId: jest.fn(() => {}), - }; -}); - -const chartsThemeService = chartPluginMock.createSetupContract().theme; -const paletteThemeService = chartPluginMock.createSetupContract().palettes; +const { theme: chartsThemeService, palettes: paletteThemeService } = + chartPluginMock.createSetupContract(); const formatService = fieldFormatsServiceMock.createStartContract(); const args: GaugeArguments = { labelMajor: 'Gauge', @@ -113,13 +96,17 @@ describe('GaugeComponent', function () { }; }); + beforeEach(() => { + jest.clearAllMocks(); + }); + it('renders the chart', () => { const component = shallowWithIntl(); expect(component.find(Chart)).toMatchSnapshot(); }); it('returns null when metric is not provided', async () => { - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -134,7 +121,7 @@ describe('GaugeComponent', function () { }); it('shows empty placeholder when minimum accessor equals maximum accessor', async () => { - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -148,7 +135,7 @@ describe('GaugeComponent', function () { expect(component.find('EmptyPlaceholder')).toHaveLength(1); }); it('shows empty placeholder when minimum accessor value is greater maximum accessor value', async () => { - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -162,7 +149,7 @@ describe('GaugeComponent', function () { expect(component.find('EmptyPlaceholder')).toHaveLength(1); }); it('when metric value is bigger than max, it takes maximum value', () => { - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -180,7 +167,7 @@ describe('GaugeComponent', function () { describe('labelMajor and labelMinor settings', () => { it('displays no labelMajor and no labelMinor when no passed', () => { - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -194,7 +181,7 @@ describe('GaugeComponent', function () { expect(datum?.subtitle).toEqual(''); }); it('displays custom labelMajor and labelMinor when passed', () => { - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -209,7 +196,7 @@ describe('GaugeComponent', function () { expect(datum?.subtitle).toEqual('custom labelMinor'); }); it('displays auto labelMajor', () => { - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -237,7 +224,7 @@ describe('GaugeComponent', function () { rangeMax: 4, }, }; - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -267,7 +254,7 @@ describe('GaugeComponent', function () { rangeMax: 4, }, }; - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -295,7 +282,7 @@ describe('GaugeComponent', function () { rangeMax: 100, }, }; - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -323,7 +310,7 @@ describe('GaugeComponent', function () { rangeMax: 10, }, }; - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -351,7 +338,7 @@ describe('GaugeComponent', function () { rangeMax: 30, }, }; - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -379,7 +366,7 @@ describe('GaugeComponent', function () { rangeMax: 10, }, }; - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -409,7 +396,7 @@ describe('GaugeComponent', function () { rangeMax: 100, }, }; - const customProps = { + const customProps: GaugeRenderProps = { ...wrapperProps, args: { ...wrapperProps.args, @@ -442,4 +429,119 @@ describe('GaugeComponent', function () { expect(settingsComponent.prop('ariaUseDefaultSummary')).toEqual(true); }); }); + + describe('formatting', () => { + let baseFormattingProps: GaugeRenderProps; + const metricFormat: ExpressionValueVisDimension['format'] = { + id: 'bytes', + }; + const tableFormatParams = { + id: 'number', + params: { + pattern: '0,00000', + }, + }; + const metricMetaParams: DatatableColumnMeta = { + type: 'number', + params: { + id: 'test', + params: { + pattern: '000,000.00', + }, + }, + }; + const createColumnsWithMetricParams = (params?: DatatableColumnMeta['params']) => + wrapperProps.data.columns.map((c) => + c.id !== 'metric-accessor' + ? c + : { + ...c, + meta: { + ...c.meta, + params, + }, + } + ); + + beforeAll(() => { + baseFormattingProps = { + ...wrapperProps, + args: { + ...wrapperProps.args, + metric: { + ...(wrapperProps.args.metric as ExpressionValueVisDimension), + accessor: { + id: 'metric-accessor', + name: 'metric-accessor', + meta: metricMetaParams, + }, + format: metricFormat, + }, + }, + data: { + ...wrapperProps.data, + columns: createColumnsWithMetricParams(tableFormatParams), + }, + }; + }); + + it('should use custom metric format params, if provided', () => { + shallowWithIntl(); + expect(formatService.deserialize).toBeCalledWith(metricFormat); + }); + + it('should use table metric format params, if provided', () => { + const customProps: GaugeRenderProps = { + ...baseFormattingProps, + args: { + ...baseFormattingProps.args, + metric: 'metric-accessor', + }, + }; + shallowWithIntl(); + expect(formatService.deserialize).toBeCalledWith(tableFormatParams); + }); + + it('should use default metric format params, if no others provided', () => { + const testParams = { + id: 'test', + }; + const customProps: GaugeRenderProps = { + ...baseFormattingProps, + args: { + ...baseFormattingProps.args, + metric: 'metric-accessor', + }, + data: { + ...baseFormattingProps.data, + columns: createColumnsWithMetricParams(testParams), + }, + }; + + shallowWithIntl(); + expect(formatService.deserialize).toBeCalledWith(testParams); + }); + + it('should use fallback if no default metric format and no others provided', () => { + const customProps: GaugeRenderProps = { + ...baseFormattingProps, + args: { + ...baseFormattingProps.args, + metric: 'metric-accessor', + }, + data: { + ...wrapperProps.data, + columns: createColumnsWithMetricParams(), + }, + }; + + shallowWithIntl(); + expect(formatService.deserialize).toBeCalledWith({ + id: 'number', + params: { + pattern: '0,0.0', + }, + }); + }); + }); }); diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx index 714ac6f3e057e..0e1ebe988e602 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.tsx @@ -14,7 +14,11 @@ import { FieldFormat } from '@kbn/field-formats-plugin/common'; import type { CustomPaletteState } from '@kbn/charts-plugin/public'; import { EmptyPlaceholder } from '@kbn/charts-plugin/public'; import { getOverridesFor } from '@kbn/chart-expressions-common'; -import { findAccessor, isVisDimension } from '@kbn/visualizations-plugin/common/utils'; +import { + findAccessor, + getFormatByAccessor, + isVisDimension, +} from '@kbn/visualizations-plugin/common/utils'; import { i18n } from '@kbn/i18n'; import { GaugeRenderProps, @@ -307,10 +311,11 @@ export const GaugeComponent: FC = ({ ? metricColumn?.meta?.params : undefined; - const defaultMetricFormatParams = { + const defaultMetricFormatParams = (args.metric && + getFormatByAccessor(args.metric, data.columns)) || { id: 'number', params: { - pattern: max - min > 5 ? `0,0` : `0,0.0`, + pattern: max - min > 5 ? '0,0' : '0,0.0', }, }; diff --git a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.test.tsx b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.test.tsx index bf9524c186134..86d40144c67bd 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.test.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.test.tsx @@ -30,15 +30,6 @@ import HeatmapComponent from './heatmap_component'; import { LegendSize } from '@kbn/visualizations-plugin/common'; import { FieldFormat } from '@kbn/field-formats-plugin/common'; -jest.mock('@elastic/charts', () => { - const original = jest.requireActual('@elastic/charts'); - - return { - ...original, - getSpecId: jest.fn(() => {}), - }; -}); - const actWithTimeout = (action: Function, timer: number = 1) => act( () => diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx index b5b2fb553676f..9d327c1272b7c 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx @@ -28,15 +28,6 @@ import { ChartTypes } from '../../common/types'; import { LegendSize } from '@kbn/visualizations-plugin/common'; import { cloneDeep } from 'lodash'; -jest.mock('@elastic/charts', () => { - const original = jest.requireActual('@elastic/charts'); - - return { - ...original, - getSpecId: jest.fn(() => {}), - }; -}); - const actWithTimeout = (action: Function, timer: number = 1) => act( () => diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_color_picker.test.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_color_picker.test.tsx index 04aa0136dcadd..14164045326c7 100644 --- a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_color_picker.test.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_color_picker.test.tsx @@ -23,15 +23,6 @@ import { createMockBucketColumns, createMockVisData } from '../mocks'; const bucketColumns = createMockBucketColumns(); const visData = createMockVisData(); -jest.mock('@elastic/charts', () => { - const original = jest.requireActual('@elastic/charts'); - - return { - ...original, - getSpecId: jest.fn(() => {}), - }; -}); - describe('LegendColorPickerWrapper', () => { const mockState = new Map(); const uiState = { diff --git a/src/plugins/data/common/search/expressions/esql.ts b/src/plugins/data/common/search/expressions/esql.ts index 7ed11099c4bef..d9893c8c203f6 100644 --- a/src/plugins/data/common/search/expressions/esql.ts +++ b/src/plugins/data/common/search/expressions/esql.ts @@ -17,6 +17,7 @@ import { Observable, defer, throwError } from 'rxjs'; import { catchError, map, switchMap, tap } from 'rxjs'; import { buildEsQuery } from '@kbn/es-query'; import type { ESQLSearchReponse, ESQLSearchParams } from '@kbn/es-types'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import { getEsQueryConfig } from '../../es_query'; import { getTime } from '../../query'; import { @@ -130,6 +131,7 @@ export const getEsqlFn = ({ getStartDependencies }: EsqlFnArguments) => { query, // time_zone: timezone, locale, + version: ESQL_LATEST_VERSION, }; if (input) { const esQueryConfigs = getEsQueryConfig( diff --git a/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.test.ts b/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.test.ts index b88683157db03..b1e36954e0b67 100644 --- a/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.test.ts +++ b/src/plugins/data/server/search/strategies/esql_async_search/esql_async_search_strategy.test.ts @@ -8,6 +8,7 @@ import { firstValueFrom } from 'rxjs'; import { KbnServerError } from '@kbn/kibana-utils-plugin/server'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import { KbnSearchError } from '../../report_search_error'; import { errors } from '@elastic/elasticsearch'; import indexNotFoundException from '../../../../common/search/test_data/index_not_found_exception.json'; @@ -66,6 +67,7 @@ describe('ES|QL async search strategy', () => { const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger); const params = { query: 'from logs* | limit 10', + version: ESQL_LATEST_VERSION, }; await esSearch .search( @@ -89,6 +91,7 @@ describe('ES|QL async search strategy', () => { const params = { query: 'from logs* | limit 10', + version: ESQL_LATEST_VERSION, }; const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger); @@ -108,6 +111,7 @@ describe('ES|QL async search strategy', () => { query: 'from logs* | limit 10', wait_for_completion_timeout: '10s', keep_alive: '5m', + version: ESQL_LATEST_VERSION, }; const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger); @@ -123,7 +127,7 @@ describe('ES|QL async search strategy', () => { it('sets transport options on POST requests', async () => { const transportOptions = { maxRetries: 1 }; mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); - const params = { query: 'from logs' }; + const params = { query: 'from logs', version: ESQL_LATEST_VERSION }; const esSearch = esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger); await firstValueFrom( @@ -140,6 +144,7 @@ describe('ES|QL async search strategy', () => { wait_for_completion_timeout: '100ms', keep_on_completion: false, query: 'from logs', + version: ESQL_LATEST_VERSION, }, }), expect.objectContaining({ maxRetries: 1, meta: true, signal: undefined }) @@ -150,6 +155,7 @@ describe('ES|QL async search strategy', () => { mockApiCaller.mockResolvedValueOnce(mockAsyncResponse); const params = { query: 'from logs* | limit 10', + version: ESQL_LATEST_VERSION, }; const esSearch = esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger); @@ -175,6 +181,7 @@ describe('ES|QL async search strategy', () => { const params = { query: 'from logs* | limit 10', + version: ESQL_LATEST_VERSION, }; const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger); @@ -197,6 +204,7 @@ describe('ES|QL async search strategy', () => { const params = { query: 'from logs* | limit 10', + version: ESQL_LATEST_VERSION, }; const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger); const abortController = new AbortController(); @@ -230,6 +238,7 @@ describe('ES|QL async search strategy', () => { const params = { query: 'from logs* | limit 10', + version: ESQL_LATEST_VERSION, }; const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger); @@ -253,6 +262,7 @@ describe('ES|QL async search strategy', () => { const params = { query: 'from logs* | limit 10', + version: ESQL_LATEST_VERSION, }; const esSearch = await esqlAsyncSearchStrategyProvider(mockSearchConfig, mockLogger); diff --git a/src/plugins/data/tsconfig.json b/src/plugins/data/tsconfig.json index 74fc83691ec53..c96442ccae5ed 100644 --- a/src/plugins/data/tsconfig.json +++ b/src/plugins/data/tsconfig.json @@ -53,7 +53,8 @@ "@kbn/bfetch-error", "@kbn/es-types", "@kbn/code-editor", - "@kbn/core-test-helpers-model-versions" + "@kbn/core-test-helpers-model-versions", + "@kbn/esql-utils" ], "exclude": [ "target/**/*", diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index 41a05a18da62b..344e0b36c9d0e 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -51,7 +51,6 @@ import { DiscoverHistogramLayout } from './discover_histogram_layout'; import { ErrorCallout } from '../../../../components/common/error_callout'; import { addLog } from '../../../../utils/add_log'; import { DiscoverResizableLayout } from './discover_resizable_layout'; -import { ESQLTechPreviewCallout } from './esql_tech_preview_callout'; import { PanelsToggle, PanelsToggleProps } from '../../../../components/panels_toggle'; import { sendErrorMsg } from '../../hooks/use_saved_search_messages'; @@ -72,7 +71,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { filterManager, history, spaces, - docLinks, } = useDiscoverServices(); const pageBackgroundColor = useEuiBackgroundColor('plain'); const globalQueryState = data.query.getState(); @@ -219,8 +217,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { return ( <> - {/* Temporarily display a tech preview callout for ES|QL*/} - {isPlainRecord && } { - const [hideCallout, setHideCallout] = useLocalStorage(ESQL_TECH_PREVIEW_CALLOUT, false); - - const onDismiss = useCallback(() => { - setHideCallout(true); - }, [setHideCallout]); - - if (hideCallout) { - return null; - } - - return ( - - - - ), - }} - /> - } - color="primary" - iconType="beaker" - onDismiss={onDismiss} - size="s" - /> - ); -}; diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index 806ae826bf3ab..096869d18ca51 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -34,10 +34,6 @@ import { } from '@kbn/discover-utils'; import { DEFAULT_ROWS_PER_PAGE, ROWS_PER_PAGE_OPTIONS } from '../common/constants'; -const technicalPreviewLabel = i18n.translate('discover.advancedSettings.technicalPreviewLabel', { - defaultMessage: 'technical preview', -}); - export const getUiSettings: ( docLinks: DocLinksServiceSetup, enableValidations: boolean @@ -321,7 +317,7 @@ export const getUiSettings: ( value: true, description: i18n.translate('discover.advancedSettings.enableESQLDescription', { defaultMessage: - '{technicalPreviewLabel} This tech preview feature is highly experimental--do not rely on this for production saved searches, visualizations or dashboards. This setting enables ES|QL in Discover. If you have feedback on this experience please reach out to us on {link}', + 'This setting enables ES|QL in Discover. If you have feedback on this experience please reach out to us on {link}', values: { link: `` + @@ -329,7 +325,6 @@ export const getUiSettings: ( defaultMessage: 'discuss.elastic.co/c/elastic-stack/kibana', }) + '', - technicalPreviewLabel: `[${technicalPreviewLabel}]`, }, }), requiresPageReload: true, diff --git a/src/plugins/management/public/components/management_app/management_app.tsx b/src/plugins/management/public/components/management_app/management_app.tsx index c64f8e74ff3a5..d7284c450ee4a 100644 --- a/src/plugins/management/public/components/management_app/management_app.tsx +++ b/src/plugins/management/public/components/management_app/management_app.tsx @@ -10,13 +10,13 @@ import './management_app.scss'; import React, { useState, useEffect, useCallback } from 'react'; import { BehaviorSubject } from 'rxjs'; -import { I18nProvider } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public'; import { CoreStart } from '@kbn/core/public'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; -import { reactRouterNavigate, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { reactRouterNavigate } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { KibanaPageTemplate, KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; import useObservable from 'react-use/lib/useObservable'; import { AppContextProvider } from './management_context'; @@ -32,7 +32,6 @@ import { SectionsServiceStart, NavigationCardsSubject } from '../../types'; interface ManagementAppProps { appBasePath: string; history: AppMountParameters['history']; - theme$: AppMountParameters['theme$']; dependencies: ManagementAppDependencies; } @@ -45,13 +44,8 @@ export interface ManagementAppDependencies { cardsNavigationConfig$: BehaviorSubject; } -export const ManagementApp = ({ - dependencies, - history, - theme$, - appBasePath, -}: ManagementAppProps) => { - const { setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$ } = dependencies; +export const ManagementApp = ({ dependencies, history, appBasePath }: ManagementAppProps) => { + const { coreStart, setBreadcrumbs, isSidebarEnabled$, cardsNavigationConfig$ } = dependencies; const [selectedId, setSelectedId] = useState(''); const [sections, setSections] = useState(); const isSidebarEnabled = useObservable(isSidebarEnabled$); @@ -114,30 +108,28 @@ export const ManagementApp = ({ }; return ( - - + + - - - - - + + + - - + + ); }; diff --git a/src/plugins/management/public/components/management_app/management_router.tsx b/src/plugins/management/public/components/management_app/management_router.tsx index 6c62163f2356c..3d56f097127bf 100644 --- a/src/plugins/management/public/components/management_app/management_router.tsx +++ b/src/plugins/management/public/components/management_app/management_router.tsx @@ -14,6 +14,7 @@ import { AppMountParameters, ChromeBreadcrumb, ScopedHistory, + ThemeServiceStart, } from '@kbn/core/public'; import { KibanaErrorBoundary, KibanaErrorBoundaryProvider } from '@kbn/shared-ux-error-boundary'; import { ManagementAppWrapper } from '../management_app_wrapper'; @@ -22,7 +23,7 @@ import { ManagementSection } from '../../utils'; interface ManagementRouterProps { history: AppMountParameters['history']; - theme$: AppMountParameters['theme$']; + theme: ThemeServiceStart; setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], appHistory?: ScopedHistory) => void; onAppMounted: (id: string) => void; sections: ManagementSection[]; @@ -35,7 +36,7 @@ export const ManagementRouter = memo( setBreadcrumbs, onAppMounted, sections, - theme$, + theme, analytics, }: ManagementRouterProps) => { return ( @@ -55,7 +56,7 @@ export const ManagementRouter = memo( setBreadcrumbs={setBreadcrumbs} onAppMounted={onAppMounted} history={history} - theme$={theme$} + theme={theme} /> )} /> diff --git a/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx b/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx index 6a9a5ad4baeda..8f044c15afd72 100644 --- a/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx +++ b/src/plugins/management/public/components/management_app_wrapper/management_app_wrapper.tsx @@ -8,7 +8,12 @@ import React, { createRef, Component } from 'react'; -import { ChromeBreadcrumb, AppMountParameters, ScopedHistory } from '@kbn/core/public'; +import { + ChromeBreadcrumb, + AppMountParameters, + ScopedHistory, + ThemeServiceStart, +} from '@kbn/core/public'; import classNames from 'classnames'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { ThrowIfError } from '@kbn/shared-ux-error-boundary'; @@ -20,7 +25,7 @@ interface ManagementSectionWrapperProps { setBreadcrumbs: (crumbs?: ChromeBreadcrumb[], history?: ScopedHistory) => void; onAppMounted: (id: string) => void; history: AppMountParameters['history']; - theme$: AppMountParameters['theme$']; + theme: ThemeServiceStart; } interface ManagementSectionWrapperState { @@ -40,15 +45,19 @@ export class ManagementAppWrapper extends Component< } componentDidMount() { - const { setBreadcrumbs, app, onAppMounted, history, theme$ } = this.props; + const { setBreadcrumbs, app, onAppMounted, history, theme } = this.props; const { mount, basePath } = app; const appHistory = history.createSubHistory(app.basePath); + // TODO: Remove this: it provides a deprecated field still needed in ManagementAppMountParams + const { theme$ } = theme; + const mountResult = mount({ basePath, setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => setBreadcrumbs(crumbs, appHistory), element: this.mountElementRef.current!, history: appHistory, + theme, theme$, }); diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index 264a4450e8f91..eee1b8617a49a 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -7,7 +7,7 @@ */ import { Observable } from 'rxjs'; -import { ScopedHistory, Capabilities } from '@kbn/core/public'; +import { ScopedHistory, Capabilities, ThemeServiceStart } from '@kbn/core/public'; import type { LocatorPublic } from '@kbn/share-plugin/common'; import { ChromeBreadcrumb, CoreTheme } from '@kbn/core/public'; import type { CardsNavigationComponentProps } from '@kbn/management-cards-navigation'; @@ -70,6 +70,8 @@ export interface ManagementAppMountParams { element: HTMLElement; // element the section should render into setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; history: ScopedHistory; + theme: ThemeServiceStart; + /** @deprecated - use `theme` **/ theme$: Observable; } diff --git a/src/plugins/management/tsconfig.json b/src/plugins/management/tsconfig.json index 116debbf72496..91e0e940c8fc6 100644 --- a/src/plugins/management/tsconfig.json +++ b/src/plugins/management/tsconfig.json @@ -27,6 +27,7 @@ "@kbn/serverless", "@kbn/shared-ux-error-boundary", "@kbn/deeplinks-management", + "@kbn/react-kibana-context-render", ], "exclude": [ "target/**/*" diff --git a/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.test.tsx b/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.test.tsx new file mode 100644 index 0000000000000..da9b39d97b7bf --- /dev/null +++ b/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useEffect, useState } from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; +import { usePresentationPanelTitleClickHandler } from './presentation_panel_title'; + +describe('usePresentationPanelTitleClickHandler', () => { + it('returns null when there is no element to attach listeners to', () => { + const { result } = renderHook(usePresentationPanelTitleClickHandler); + + expect(result.current).toBe(null); + }); + + it('calls the click subscribe handler when the enter button is clicked on the provided element', async () => { + const mockedClickHandler = jest.fn(); + + const TestComponent = ({ onClickHandler }: { onClickHandler: () => void }) => { + const [$elm, setElm] = useState(null); + const onClick$ = usePresentationPanelTitleClickHandler($elm); + + useEffect(() => { + const subscription = onClick$?.subscribe(onClickHandler); + + return () => subscription?.unsubscribe(); + }, [onClick$, onClickHandler]); + + return ( +
+ Hello World +
+ ); + }; + + render(); + + fireEvent.keyDown(await screen.findByTestId('syntheticClick'), { + key: 'Enter', + code: 'Enter', + charCode: 13, + }); + + expect(mockedClickHandler).toHaveBeenCalled(); + }); +}); diff --git a/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.tsx b/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.tsx index 5e277e5194f87..33c85d247a0df 100644 --- a/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.tsx +++ b/src/plugins/presentation_panel/public/panel_component/panel_header/presentation_panel_title.tsx @@ -8,7 +8,20 @@ import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui'; import classNames from 'classnames'; -import React, { useMemo } from 'react'; +import { once } from 'lodash'; +import React, { useMemo, useEffect, useRef, useState } from 'react'; +import { + type Observable, + fromEvent, + map, + race, + mergeMap, + takeUntil, + takeLast, + takeWhile, + defaultIfEmpty, + repeatWhen, +} from 'rxjs'; import { ViewMode } from '@kbn/presentation-publishing'; import { getEditTitleAriaLabel, placeholderTitle } from '../presentation_panel_strings'; @@ -18,6 +31,51 @@ import { } from '../../panel_actions/customize_panel_action'; import { openCustomizePanelFlyout } from '../../panel_actions/customize_panel_action/open_customize_panel'; +const createDocumentMouseMoveListener = once(() => fromEvent(document, 'mousemove')); +const createDocumentMouseUpListener = once(() => fromEvent(document, 'mouseup')); + +export const usePresentationPanelTitleClickHandler = (titleElmRef: HTMLElement | null) => { + const onClick = useRef | null>(null); + const [initialized, setInitialized] = useState(false); + + useEffect(() => { + if (titleElmRef) { + const mouseup = createDocumentMouseUpListener(); + const mousemove = createDocumentMouseMoveListener(); + const mousedown = fromEvent(titleElmRef, 'mousedown'); + const keydown = fromEvent(titleElmRef, 'keydown'); + + const mousedragExclusiveClick$ = mousedown + .pipe( + mergeMap(function (md) { + // create reference for when mouse is down + const startX = md.offsetX; + const startY = md.offsetY; + + return mousemove + .pipe( + map(function (mm) { + return { dragged: startX !== mm.clientX && startY !== mm.clientY }; + }) + ) + .pipe(takeUntil(mouseup), takeLast(1)) + .pipe(defaultIfEmpty({ dragged: false })); + }) + ) + .pipe(repeatWhen(() => mousedown)); + + onClick.current = race( + keydown.pipe(takeWhile((kd) => kd.key === 'Enter')).pipe(map(() => ({ dragged: false }))), + mousedragExclusiveClick$ + ); + + setInitialized(true); + } + }, [titleElmRef]); + + return initialized ? onClick.current : null; +}; + export const PresentationPanelTitle = ({ api, viewMode, @@ -31,6 +89,7 @@ export const PresentationPanelTitle = ({ panelDescription?: string; viewMode?: ViewMode; }) => { + const [panelTitleElmRef, setPanelTitleElmRef] = useState(null); const panelTitleElement = useMemo(() => { if (hideTitle) return null; const titleClassNames = classNames('embPanel__titleText', { @@ -45,20 +104,30 @@ export const PresentationPanelTitle = ({ return ( - openCustomizePanelFlyout({ - api: api as CustomizePanelActionApi, - focusOnTitle: true, - }) - } > {panelTitle || placeholderTitle} ); - }, [hideTitle, panelTitle, viewMode, api]); + }, [setPanelTitleElmRef, hideTitle, panelTitle, viewMode, api]); + + const onClick = usePresentationPanelTitleClickHandler(panelTitleElmRef); + + useEffect(() => { + const panelTitleClickSubscription = onClick?.subscribe(function onClickHandler({ dragged }) { + if (!dragged) { + openCustomizePanelFlyout({ + api: api as CustomizePanelActionApi, + focusOnTitle: true, + }); + } + }); + + return () => panelTitleClickSubscription?.unsubscribe(); + }, [api, onClick]); const describedPanelTitleElement = useMemo(() => { if (!panelDescription) { diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index eb75d3486d5d5..e9d8058ff1420 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -12,7 +12,6 @@ import { css } from '@emotion/react'; import { EuiPopover, EuiPanel, - EuiBadge, EuiHorizontalRule, EuiButton, EuiContextMenuPanel, @@ -361,11 +360,6 @@ export function ChangeDataView({ {i18n.translate('unifiedSearch.query.queryBar.textBasedLanguagesTryLabel', { defaultMessage: 'Try ES|QL', })} - - {i18n.translate('unifiedSearch.query.queryBar.textBasedLanguagesTechPreviewLabel', { - defaultMessage: 'Technical preview', - })} - ); diff --git a/src/plugins/visualizations/common/utils/accessors.ts b/src/plugins/visualizations/common/utils/accessors.ts index 929d0fae7b4b4..c258e06779f22 100644 --- a/src/plugins/visualizations/common/utils/accessors.ts +++ b/src/plugins/visualizations/common/utils/accessors.ts @@ -103,7 +103,7 @@ export function getFormatByAccessor( defaultColumnFormat?: SerializedFieldFormat ): SerializedFieldFormat | undefined { return typeof dimension === 'string' - ? getColumnByAccessor(dimension, columns)?.meta.params || defaultColumnFormat + ? getColumnByAccessor(dimension, columns)?.meta?.params || defaultColumnFormat : dimension.format || defaultColumnFormat; } diff --git a/test/api_integration/apis/esql/errors.ts b/test/api_integration/apis/esql/errors.ts index ad4e251a8f364..9a79706a43ae3 100644 --- a/test/api_integration/apis/esql/errors.ts +++ b/test/api_integration/apis/esql/errors.ts @@ -11,6 +11,7 @@ import Path from 'path'; import expect from '@kbn/expect'; import { MappingProperty } from '@elastic/elasticsearch/lib/api/types'; import { REPO_ROOT } from '@kbn/repo-info'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import uniqBy from 'lodash/uniqBy'; import { groupBy, mapValues } from 'lodash'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -123,6 +124,7 @@ export default function ({ getService }: FtrProviderContext) { path: '/_query', body: { query, + version: ESQL_LATEST_VERSION, }, }); return { resp, error: undefined }; diff --git a/test/functional/apps/discover/group3/_lens_vis.ts b/test/functional/apps/discover/group3/_lens_vis.ts index 5747dbd85de64..d83cbe992e929 100644 --- a/test/functional/apps/discover/group3/_lens_vis.ts +++ b/test/functional/apps/discover/group3/_lens_vis.ts @@ -107,7 +107,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { return await chartElement.getAttribute('data-title'); } - describe('discover lens vis', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/180404 + describe.skip('discover lens vis', function describeIndexTests() { before(async () => { await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); diff --git a/test/tsconfig.json b/test/tsconfig.json index c5c7467dd8bcd..ebf1a1e71c01b 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -73,5 +73,6 @@ "@kbn/links-plugin", "@kbn/ftr-common-functional-ui-services", "@kbn/monaco", + "@kbn/esql-utils", ] } diff --git a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx index 34d6f56a8594a..24a23c55e83ac 100644 --- a/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx +++ b/x-pack/packages/security-solution/ecs_data_quality_dashboard/impl/data_quality/data_quality_panel/pattern/pattern_summary/pattern_label/index.tsx @@ -56,7 +56,7 @@ const PatternLabelComponent: React.FC = ({ -

{pattern}

+

{pattern}

diff --git a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap index f27cc6ab83623..f85e0abce89c7 100644 --- a/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap +++ b/x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap @@ -30,6 +30,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -114,6 +119,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -203,6 +213,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -219,6 +234,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, "stopSequences": Object { @@ -244,6 +264,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "system": Object { @@ -252,6 +277,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -268,6 +298,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "number", }, }, @@ -349,6 +384,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -365,6 +405,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, "stopSequences": Object { @@ -390,6 +435,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "system": Object { @@ -398,6 +448,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -414,6 +469,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "number", }, }, @@ -855,6 +915,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + ], "rules": Array [ Object { "args": Object { @@ -1520,6 +1585,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1536,6 +1606,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1552,6 +1627,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1588,6 +1668,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1604,6 +1689,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1620,6 +1710,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2504,6 +2599,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + ], "rules": Array [ Object { "args": Object { @@ -3136,6 +3236,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + ], "rules": Array [ Object { "args": Object { @@ -3560,6 +3665,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 50, + }, + ], "rules": Array [ Object { "args": Object { @@ -3577,6 +3687,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3593,6 +3708,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3609,6 +3729,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 15000, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3631,6 +3759,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3674,6 +3810,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 512, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3694,6 +3838,14 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + Object { + "x-oas-max-length": 130, + }, + ], "rules": Array [ Object { "args": Object { @@ -3728,6 +3880,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 25000, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3812,6 +3972,11 @@ Object { }, }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "alternatives", }, "responders": Object { @@ -4052,6 +4217,11 @@ Object { "type": "alternatives", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4068,6 +4238,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 100, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4096,6 +4274,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 50, + }, + ], "rules": Array [ Object { "args": Object { @@ -4113,6 +4296,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4129,6 +4317,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 100, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4334,6 +4530,11 @@ Object { "type": "alternatives", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4384,6 +4585,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 25000, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4406,6 +4615,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 100, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4428,6 +4645,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 100, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4681,6 +4906,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4697,6 +4927,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4713,6 +4948,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4748,6 +4991,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 255, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4808,6 +5059,11 @@ Object { }, }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "alternatives", }, "group": Object { @@ -4816,6 +5072,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4879,6 +5140,11 @@ Object { "type": "object", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "severity": Object { @@ -4937,6 +5203,11 @@ Object { }, }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "alternatives", }, "source": Object { @@ -4945,6 +5216,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4961,6 +5237,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 1024, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4983,6 +5267,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -6543,6 +6832,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "appsVulnerabilityStatuses": Object { @@ -12250,6 +12544,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "appsVulnerabilityStatuses": Object { @@ -17957,6 +18256,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "appsVulnerabilityStatuses": Object { @@ -23664,6 +23968,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "appsVulnerabilityStatuses": Object { @@ -28663,6 +28972,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "computerName": Object { @@ -28671,6 +28985,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -28696,6 +29015,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -28712,6 +29036,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -28728,6 +29057,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -28744,6 +29078,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -28767,6 +29106,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -28783,6 +29127,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -28794,6 +29143,11 @@ Object { "type": "string", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -28807,6 +29161,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "scriptId": Object { @@ -28829,6 +29188,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -28845,6 +29209,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "number", }, "singularityxdrKeyword": Object { @@ -28853,6 +29222,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -28869,6 +29243,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -28885,6 +29264,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -32824,6 +33208,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -32880,6 +33269,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -32900,6 +33294,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -32925,6 +33324,11 @@ Object { "type": "object", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -32959,6 +33363,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -33026,6 +33435,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -33105,6 +33519,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -33138,6 +33557,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -33152,6 +33576,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -33237,6 +33666,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -33270,6 +33704,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -34626,6 +35065,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -34764,6 +35208,11 @@ Object { "type": "number", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -34777,6 +35226,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -34883,6 +35337,11 @@ Object { "type": "number", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -34896,6 +35355,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -35205,6 +35669,11 @@ Object { }, }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "alternatives", }, "ca": Object { @@ -35213,6 +35682,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -35255,6 +35729,11 @@ Object { }, }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "alternatives", }, "hasAuth": Object { @@ -35277,6 +35756,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + ], "rules": Array [ Object { "args": Object { @@ -35422,6 +35906,11 @@ Object { }, }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "alternatives", }, }, @@ -35668,6 +36157,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -35904,6 +36398,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -35920,6 +36419,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -35936,6 +36440,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -35966,6 +36475,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -35982,6 +36496,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -35998,6 +36517,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { diff --git a/x-pack/plugins/alerting/public/application/maintenance_windows.tsx b/x-pack/plugins/alerting/public/application/maintenance_windows.tsx index bb2ba1847abac..5f4b81bb716f7 100644 --- a/x-pack/plugins/alerting/public/application/maintenance_windows.tsx +++ b/x-pack/plugins/alerting/public/application/maintenance_windows.tsx @@ -7,17 +7,18 @@ import React, { Suspense } from 'react'; import ReactDOM from 'react-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { Router, Routes, Route } from '@kbn/shared-ux-router'; + +import { EuiLoadingSpinner } from '@elastic/eui'; import { CoreStart } from '@kbn/core/public'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { ManagementAppMountParams } from '@kbn/management-plugin/public'; -import { EuiLoadingSpinner } from '@elastic/eui'; -import { AlertingPluginStart } from '../plugin'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { Route, Router, Routes } from '@kbn/shared-ux-router'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MAINTENANCE_WINDOW_PATHS } from '../../common'; import { useLicense } from '../hooks/use_license'; +import { AlertingPluginStart } from '../plugin'; const MaintenanceWindowsLazy: React.FC = React.lazy(() => import('../pages/maintenance_windows')); const MaintenanceWindowsCreateLazy: React.FC = React.lazy( @@ -76,14 +77,13 @@ export const renderApp = ({ mountParams: ManagementAppMountParams; kibanaVersion: string; }) => { - const { element, history, theme$ } = mountParams; - const i18nCore = core.i18n; - const isDarkMode = core.theme.getTheme().darkMode; + const { element, history } = mountParams; + const { i18n, theme } = core; const queryClient = new QueryClient(); ReactDOM.render( - + - - - - - - - + + + - , + , element ); return () => { diff --git a/x-pack/plugins/alerting/public/lib/test_utils.tsx b/x-pack/plugins/alerting/public/lib/test_utils.tsx index 6e1642bfe0d36..9c7b59a1527c4 100644 --- a/x-pack/plugins/alerting/public/lib/test_utils.tsx +++ b/x-pack/plugins/alerting/public/lib/test_utils.tsx @@ -6,14 +6,14 @@ */ import React from 'react'; -import { of, BehaviorSubject } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { I18nProvider } from '@kbn/i18n-react'; -import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react'; import { Capabilities, CoreStart } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; -import { euiDarkVars } from '@kbn/ui-theme'; import type { ILicense } from '@kbn/licensing-plugin/public'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; @@ -40,8 +40,6 @@ export const createAppMockRenderer = ({ capabilities, license, }: AppMockRendererArgs = {}): AppMockRenderer => { - const theme$ = of({ eui: euiDarkVars, darkMode: true }); - const licensingPluginMock = licensingMock.createStart(); const queryClient = new QueryClient({ @@ -83,7 +81,7 @@ export const createAppMockRenderer = ({ }; const AppWrapper: React.FC<{ children: React.ReactElement }> = React.memo(({ children }) => ( - + {children} diff --git a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap index af6c6fc130022..bbfa2fb3bb28b 100644 --- a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap +++ b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap @@ -16,6 +16,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -87,6 +95,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -148,6 +161,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -244,6 +262,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -442,6 +465,11 @@ Object { "type": "object", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -464,6 +492,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -521,6 +554,11 @@ Object { }, }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "alternatives", }, "termSize": Object { @@ -529,6 +567,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -679,6 +722,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -704,6 +755,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -789,6 +845,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -809,6 +870,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -831,12 +897,22 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, "boundaryIndexTitle": Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -859,6 +935,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -879,6 +963,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -899,6 +988,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -919,6 +1013,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -939,6 +1038,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -959,6 +1063,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -979,6 +1088,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -1001,6 +1115,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, }, @@ -1029,6 +1148,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1073,6 +1200,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1121,6 +1253,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -1149,6 +1286,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -1186,6 +1328,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1208,6 +1358,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1339,6 +1494,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-min-length": 1, + }, + ], "rules": Array [ Object { "args": Object { @@ -1554,6 +1714,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1570,6 +1735,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1640,6 +1810,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1673,6 +1848,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "searchConfiguration": Object { @@ -1731,6 +1911,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + ], "rules": Array [ Object { "args": Object { @@ -1773,6 +1958,11 @@ Object { "type": "object", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -1786,6 +1976,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -1808,6 +2003,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "windowSize": Object { @@ -1931,6 +2131,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "searchConfiguration": Object { @@ -1989,6 +2194,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + ], "rules": Array [ Object { "args": Object { @@ -2031,6 +2241,11 @@ Object { "type": "object", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -2044,6 +2259,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2066,6 +2286,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2082,6 +2307,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2098,6 +2328,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "windowSize": Object { @@ -2177,6 +2412,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "searchConfiguration": Object { @@ -2235,6 +2475,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + ], "rules": Array [ Object { "args": Object { @@ -2277,6 +2522,11 @@ Object { "type": "object", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -2290,6 +2540,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2312,6 +2567,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2328,6 +2588,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2344,6 +2609,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "windowSize": Object { @@ -2393,6 +2663,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "criteria": Object { @@ -2490,6 +2765,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2511,6 +2791,11 @@ Object { "type": "any", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -2579,6 +2864,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2610,6 +2900,11 @@ Object { "type": "number", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, }, @@ -2629,6 +2924,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2700,6 +3000,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "alertOnNoData": Object { @@ -2708,6 +3013,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "criteria": Object { @@ -2734,6 +3044,11 @@ Object { "only": true, "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, "comparator": Object { @@ -2762,6 +3077,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2778,6 +3098,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -2913,6 +3238,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3053,6 +3383,11 @@ Object { }, }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "alternatives", }, "searchConfiguration": Object { @@ -3084,6 +3419,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + ], "rules": Array [ Object { "args": Object { @@ -3119,6 +3459,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3157,6 +3505,11 @@ Object { "type": "object", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "index": Object { @@ -3196,6 +3549,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "allowNoIndex": Object { @@ -3204,6 +3562,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "fieldAttrs": Object { @@ -3212,6 +3575,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3244,6 +3615,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "number", }, "customDescription": Object { @@ -3252,6 +3628,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 300, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3274,6 +3658,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3304,6 +3693,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3336,6 +3733,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3352,6 +3754,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, }, @@ -3374,6 +3781,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3406,6 +3821,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "count": Object { @@ -3414,6 +3834,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3430,6 +3855,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 300, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3452,6 +3885,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3485,6 +3923,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "format": Object { @@ -3500,6 +3943,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3516,9 +3964,19 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -3530,6 +3988,11 @@ Object { "flags": Object { "error": [Function], }, + "metas": Array [ + Object { + "x-oas-max-length": 1000, + }, + ], "rules": Array [ Object { "args": Object { @@ -3552,6 +4015,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "runtimeField": Object { @@ -3577,6 +4045,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 300, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3599,6 +4075,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3622,6 +4103,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3638,9 +4124,19 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -3654,6 +4150,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3686,6 +4187,11 @@ Object { "type": "string", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -3810,6 +4316,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3842,6 +4356,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 300, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3864,6 +4386,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3887,6 +4414,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -3903,9 +4435,19 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -3919,6 +4461,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4057,6 +4604,11 @@ Object { "type": "string", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -4084,6 +4636,11 @@ Object { }, }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "alternatives", }, "script": Object { @@ -4092,6 +4649,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 1000000, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4114,6 +4679,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "searchable": Object { @@ -4122,6 +4692,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "shortDotsEnable": Object { @@ -4130,6 +4705,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "boolean", }, "subType": Object { @@ -4161,6 +4741,11 @@ Object { "type": "string", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -4190,6 +4775,11 @@ Object { "type": "string", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -4198,6 +4788,11 @@ Object { "type": "object", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -4211,6 +4806,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 1000, + }, + ], "rules": Array [ Object { "args": Object { @@ -4247,6 +4847,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4263,6 +4868,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4296,6 +4906,11 @@ Object { "type": "string", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "runtimeFieldMap": Object { @@ -4304,6 +4919,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4342,6 +4965,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 300, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4364,6 +4995,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4387,6 +5023,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4403,9 +5044,19 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -4419,6 +5070,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4451,6 +5107,11 @@ Object { "type": "string", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -4575,6 +5236,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-get-additional-properties": [Function], + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4607,6 +5276,14 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-max-length": 300, + }, + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4629,6 +5306,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4652,6 +5334,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4668,9 +5355,19 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "any", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -4684,6 +5381,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4822,6 +5524,11 @@ Object { "type": "string", }, }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -4905,6 +5612,11 @@ Object { }, }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "alternatives", }, "value": Object { @@ -4930,6 +5642,11 @@ Object { "type": "object", }, ], + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "type": "array", }, "timeFieldName": Object { @@ -4938,6 +5655,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4968,6 +5690,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -4986,6 +5713,11 @@ Object { "unknown": true, }, "keys": Object {}, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "preferences": Object { "stripUnknown": Object { "objects": false, @@ -4999,6 +5731,11 @@ Object { "error": [Function], "presence": "optional", }, + "metas": Array [ + Object { + "x-oas-optional": true, + }, + ], "rules": Array [ Object { "args": Object { @@ -6811,6 +7548,52 @@ Object { }, Object { "properties": Object { + "alertSuppression": Object { + "additionalProperties": false, + "properties": Object { + "duration": Object { + "additionalProperties": false, + "properties": Object { + "unit": Object { + "enum": Array [ + "s", + "m", + "h", + ], + "type": "string", + }, + "value": Object { + "minimum": 1, + "type": "integer", + }, + }, + "required": Array [ + "value", + "unit", + ], + "type": "object", + }, + "groupBy": Object { + "items": Object { + "type": "string", + }, + "maxItems": 3, + "minItems": 1, + "type": "array", + }, + "missingFieldsStrategy": Object { + "enum": Array [ + "doNotSuppress", + "suppress", + ], + "type": "string", + }, + }, + "required": Array [ + "groupBy", + ], + "type": "object", + }, "dataViewId": Object { "type": "string", }, diff --git a/x-pack/plugins/alerting/tsconfig.json b/x-pack/plugins/alerting/tsconfig.json index de830800f5aea..2158a9d7d33a8 100644 --- a/x-pack/plugins/alerting/tsconfig.json +++ b/x-pack/plugins/alerting/tsconfig.json @@ -68,7 +68,9 @@ "@kbn/core-ui-settings-server-mocks", "@kbn/core-test-helpers-kbn-server", "@kbn/core-http-router-server-internal", - "@kbn/core-execution-context-server-mocks" + "@kbn/core-execution-context-server-mocks", + "@kbn/react-kibana-context-render", + "@kbn/react-kibana-context-theme" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/cases/public/common/use_cases_toast.tsx b/x-pack/plugins/cases/public/common/use_cases_toast.tsx index 624c46aaf5a4f..71a9e3add1ae4 100644 --- a/x-pack/plugins/cases/public/common/use_cases_toast.tsx +++ b/x-pack/plugins/cases/public/common/use_cases_toast.tsx @@ -9,7 +9,7 @@ import type { ErrorToastOptions } from '@kbn/core/public'; import { EuiButtonEmpty, EuiText, logicalCSS, useEuiTheme } from '@elastic/eui'; import React, { useMemo } from 'react'; import { css } from '@emotion/react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { isValidOwner } from '../../common/utils/owner'; import type { CaseUI } from '../../common'; import { AttachmentType } from '../../common/types/domain'; @@ -106,7 +106,8 @@ const getErrorMessage = (error: Error | ServerError): string => { export const useCasesToast = () => { const { appId } = useApplication(); - const { getUrlForApp, navigateToUrl } = useKibana().services.application; + const { application, i18n, theme } = useKibana().services; + const { getUrlForApp, navigateToUrl } = application; const toasts = useToasts(); @@ -147,12 +148,13 @@ export const useCasesToast = () => { return toasts.addSuccess({ color: 'success', iconType: 'check', - title: toMountPoint(), + title: toMountPoint(, { i18n, theme }), text: toMountPoint( + />, + { i18n, theme } ), }); }, @@ -175,7 +177,7 @@ export const useCasesToast = () => { }); }, }), - [appId, getUrlForApp, navigateToUrl, toasts] + [i18n, theme, appId, getUrlForApp, navigateToUrl, toasts] ); }; diff --git a/x-pack/plugins/cases/public/common/use_is_dark_theme.test.tsx b/x-pack/plugins/cases/public/common/use_is_dark_theme.test.tsx deleted file mode 100644 index 460fac7c01be5..0000000000000 --- a/x-pack/plugins/cases/public/common/use_is_dark_theme.test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { BehaviorSubject } from 'rxjs'; -import { renderHook } from '@testing-library/react-hooks'; -import { useKibana } from './lib/kibana'; -import { useIsDarkTheme } from './use_is_dark_theme'; -import type { CoreTheme } from '@kbn/core-theme-browser'; -import type { AppMockRenderer } from './mock'; -import { createAppMockRenderer } from './mock'; - -jest.mock('./lib/kibana'); - -const useKibanaMock = useKibana as jest.Mocked; - -describe('useIsDarkTheme', () => { - let appMockRender: AppMockRenderer; - - beforeEach(() => { - appMockRender = createAppMockRenderer(); - useKibanaMock().services.theme.theme$ = new BehaviorSubject({ darkMode: true }); - }); - - it('returns true if the theme is in dark mode', async () => { - const { result } = renderHook(() => useIsDarkTheme(), { - wrapper: appMockRender.AppWrapper, - }); - - expect(result.current).toBe(true); - }); - - it('returns the default theme if the theme service is undefined', async () => { - // @ts-expect-error: service maybe undefined - useKibanaMock().services.theme = undefined; - - const { result } = renderHook(() => useIsDarkTheme(), { - wrapper: appMockRender.AppWrapper, - }); - - expect(result.current).toBe(false); - }); -}); diff --git a/x-pack/plugins/cases/public/common/use_is_dark_theme.tsx b/x-pack/plugins/cases/public/common/use_is_dark_theme.tsx deleted file mode 100644 index 4e6120d953a27..0000000000000 --- a/x-pack/plugins/cases/public/common/use_is_dark_theme.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useMemo } from 'react'; -import { of } from 'rxjs'; -import useObservable from 'react-use/lib/useObservable'; -import { useKibana } from './lib/kibana'; - -const themeDefault = { darkMode: false }; - -/** - * Indicates if the currently applied theme is either dark or light. - * @return {boolean} - Returns true if the currently applied theme is dark. - */ -export function useIsDarkTheme(): boolean { - const { - services: { theme }, - } = useKibana(); - - const themeObservable$ = useMemo(() => { - return theme?.theme$ ?? of(themeDefault); - }, [theme]); - - const { darkMode } = useObservable(themeObservable$, themeDefault); - - return darkMode; -} diff --git a/x-pack/plugins/cases/public/components/visualizations/actions/action_wrapper.tsx b/x-pack/plugins/cases/public/components/visualizations/actions/action_wrapper.tsx index 798ddea708193..543969c731d36 100644 --- a/x-pack/plugins/cases/public/components/visualizations/actions/action_wrapper.tsx +++ b/x-pack/plugins/cases/public/components/visualizations/actions/action_wrapper.tsx @@ -8,9 +8,8 @@ import type { PropsWithChildren } from 'react'; import React from 'react'; import { Router } from '@kbn/shared-ux-router'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; -import { useIsDarkTheme } from '../../../common/use_is_dark_theme'; import { SECURITY_SOLUTION_OWNER } from '../../../../common'; import type { CasesUIActionProps } from './types'; import { KibanaContextProvider, useKibana } from '../../../common/lib/kibana'; @@ -30,8 +29,7 @@ const ActionWrapperWithContext: React.FC> = ({ caseContextProps, currentAppId, }) => { - const { application } = useKibana().services; - const isDarkTheme = useIsDarkTheme(); + const { application, i18n, theme } = useKibana().services; const owner = getCaseOwnerByAppId(currentAppId); const casePermissions = canUseCases(application.capabilities)(owner ? [owner] : undefined); @@ -39,7 +37,7 @@ const ActionWrapperWithContext: React.FC> = ({ const syncAlerts = owner === SECURITY_SOLUTION_OWNER; return ( - + > = ({ > {children} - + ); }; diff --git a/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.test.tsx b/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.test.tsx index 6742242b71fb2..1e1eb398c991b 100644 --- a/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.test.tsx +++ b/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.test.tsx @@ -14,7 +14,7 @@ import { createAddToExistingCaseLensAction } from './add_to_existing_case'; import type { ActionContext } from './types'; import { useCasesAddToExistingCaseModal } from '../../all_cases/selector_modal/use_cases_add_to_existing_case_modal'; import React from 'react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { getMockApplications$, getMockCaseUiActionProps, @@ -44,10 +44,13 @@ jest.mock('../../../client/helpers/can_use_cases', () => { }); jest.mock('@kbn/kibana-react-plugin/public', () => ({ - toMountPoint: jest.fn(), KibanaThemeProvider: jest.fn().mockImplementation(({ children }) => <>{children}), })); +jest.mock('@kbn/react-kibana-mount', () => ({ + toMountPoint: jest.fn(), +})); + jest.mock('../../../common/lib/kibana', () => { return { useKibana: jest.fn(), diff --git a/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.tsx b/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.tsx index b71248353ec54..647abe8b41f2a 100644 --- a/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.tsx +++ b/x-pack/plugins/cases/public/components/visualizations/actions/add_to_existing_case.tsx @@ -10,7 +10,7 @@ import type { Embeddable as LensEmbeddable } from '@kbn/lens-plugin/public'; import { createAction } from '@kbn/ui-actions-plugin/public'; import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import type { CaseUI } from '../../../../common'; import { isLensEmbeddable, hasInput, getLensCaseAttachment } from './utils'; @@ -59,7 +59,7 @@ export const createAddToExistingCaseLensAction = ({ history, caseContextProps, }: CasesUIActionProps) => { - const { application: applicationService, theme } = core; + const { application: applicationService, i18n, theme } = core; let currentAppId: string | undefined; @@ -124,7 +124,7 @@ export const createAddToExistingCaseLensAction = ({ onSuccess={onSuccess} /> , - { theme$: theme.theme$ } + { i18n, theme } ); mount(targetDomElement); diff --git a/x-pack/plugins/cases/public/components/visualizations/actions/mocks.ts b/x-pack/plugins/cases/public/components/visualizations/actions/mocks.ts index 7957b24b1ba48..aee3b128d554b 100644 --- a/x-pack/plugins/cases/public/components/visualizations/actions/mocks.ts +++ b/x-pack/plugins/cases/public/components/visualizations/actions/mocks.ts @@ -4,23 +4,16 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { CoreTheme, PublicAppInfo } from '@kbn/core/public'; -import { BehaviorSubject, of } from 'rxjs'; -import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; -import { createBrowserHistory } from 'history'; -import type { CasesUIActionProps } from './types'; -const mockTheme: CoreTheme = { - darkMode: false, -}; +import { createBrowserHistory } from 'history'; +import { BehaviorSubject } from 'rxjs'; -const createThemeMock = (): CoreTheme => { - return { ...mockTheme }; -}; +import type { PublicAppInfo } from '@kbn/core/public'; +import { coreMock } from '@kbn/core/public/mocks'; +import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import type { CasesUIActionProps } from './types'; -export const createTheme$Mock = () => { - return of(createThemeMock()); -}; +const coreStart = coreMock.createStart(); export class MockEmbeddable { public type; @@ -76,8 +69,8 @@ export const getMockApplications$ = () => export const getMockCaseUiActionProps = () => { const core = { + ...coreStart, application: { currentAppId$: getMockCurrentAppId$(), capabilities: {} }, - theme: { theme$: createTheme$Mock() }, uiSettings: { get: jest.fn().mockReturnValue(true), }, diff --git a/x-pack/plugins/cases/tsconfig.json b/x-pack/plugins/cases/tsconfig.json index 1a1f8e56543bf..e493964cc088e 100644 --- a/x-pack/plugins/cases/tsconfig.json +++ b/x-pack/plugins/cases/tsconfig.json @@ -64,7 +64,6 @@ "@kbn/ui-actions-plugin", "@kbn/core-lifecycle-browser", "@kbn/core-saved-objects-api-server-mocks", - "@kbn/core-theme-browser", "@kbn/serverless", "@kbn/core-http-server", "@kbn/alerting-plugin", @@ -73,6 +72,7 @@ "@kbn/rison", "@kbn/core-application-browser", "@kbn/react-kibana-context-render", + "@kbn/react-kibana-mount", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_overall_stats_data.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_overall_stats_data.ts index 1d5e2e4626e70..3cb59da17ad8c 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_overall_stats_data.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/esql/use_esql_overall_stats_data.ts @@ -14,7 +14,7 @@ import { type UseCancellableSearch, useCancellableSearch } from '@kbn/ml-cancell import type { estypes } from '@elastic/elasticsearch'; import type { ISearchOptions } from '@kbn/data-plugin/common'; import type { TimeBucketsInterval } from '@kbn/ml-time-buckets'; -import { getESQLWithSafeLimit } from '@kbn/esql-utils'; +import { getESQLWithSafeLimit, ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import { OMIT_FIELDS } from '../../../../../common/constants'; import type { DataStatsFetchProgress, @@ -84,6 +84,7 @@ const getESQLDocumentCountStats = async ( params: { query: esqlBaseQuery + aggQuery, ...(filter ? { filter } : {}), + version: ESQL_LATEST_VERSION, }, }; try { @@ -129,6 +130,7 @@ const getESQLDocumentCountStats = async ( params: { query: esqlBaseQuery + ' | STATS _count_ = COUNT(*) | LIMIT 1', ...(filter ? { filter } : {}), + version: ESQL_LATEST_VERSION, }, }; try { @@ -247,6 +249,7 @@ export const useESQLOverallStatsData = ( params: { query: esqlBaseQuery + '| LIMIT 0', ...(filter ? { filter } : {}), + version: ESQL_LATEST_VERSION, }, }, { strategy: ESQL_SEARCH_STRATEGY } diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_count_and_cardinality.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_count_and_cardinality.ts index 41bc6e4b52b6d..9bcf5e7190fb2 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_count_and_cardinality.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_count_and_cardinality.ts @@ -9,6 +9,7 @@ import pLimit from 'p-limit'; import { chunk } from 'lodash'; import { isDefined } from '@kbn/ml-is-defined'; import type { ESQLSearchReponse } from '@kbn/es-types'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import type { UseCancellableSearch } from '@kbn/ml-cancellable-search'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { i18n } from '@kbn/i18n'; @@ -105,6 +106,7 @@ const getESQLOverallStatsInChunk = async ({ params: { query, ...(filter ? { filter } : {}), + version: ESQL_LATEST_VERSION, }, }; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_date_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_date_field_stats.ts index fb06899466576..846aadf7e17ad 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_date_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_date_field_stats.ts @@ -8,6 +8,7 @@ import type { UseCancellableSearch } from '@kbn/ml-cancellable-search'; import type { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; import { ESQL_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import type { Column } from '../../hooks/esql/use_esql_overall_stats_data'; import { getSafeESQLName } from '../requests/esql_utils'; import type { DateFieldStats, FieldStatsError } from '../../../../../common/types/field_stats'; @@ -40,6 +41,7 @@ export const getESQLDateFieldStats = async ({ params: { query: esqlBaseQuery + dateStatsQuery, ...(filter ? { filter } : {}), + version: ESQL_LATEST_VERSION, }, }; try { diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_numeric_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_numeric_field_stats.ts index 2f0ea4d7b3070..615d07abc4066 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_numeric_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_numeric_field_stats.ts @@ -8,6 +8,7 @@ import type { UseCancellableSearch } from '@kbn/ml-cancellable-search'; import type { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; import { ESQL_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import { chunk } from 'lodash'; import pLimit from 'p-limit'; import type { Column } from '../../hooks/esql/use_esql_overall_stats_data'; @@ -69,6 +70,7 @@ const getESQLNumericFieldStatsInChunk = async ({ params: { query: esqlBaseQuery + numericStatsQuery, ...(filter ? { filter } : {}), + version: ESQL_LATEST_VERSION, }, }; try { diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts index d78e286e88d31..f79e6fc9a7145 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/esql_requests/get_text_field_stats.ts @@ -8,6 +8,7 @@ import type { UseCancellableSearch } from '@kbn/ml-cancellable-search'; import type { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; import { ESQL_SEARCH_STRATEGY } from '@kbn/data-plugin/common'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import type { Column } from '../../hooks/esql/use_esql_overall_stats_data'; import type { FieldExamples, FieldStatsError } from '../../../../../common/types/field_stats'; @@ -39,6 +40,7 @@ export const getESQLExampleFieldValues = async ({ `| KEEP ${textFields.map((f) => f.name).join(',')} | LIMIT 10`, ...(filter ? { filter } : {}), + version: ESQL_LATEST_VERSION, }, }; const textFieldsResp = await runRequest(request, { strategy: ESQL_SEARCH_STRATEGY }); diff --git a/x-pack/plugins/fleet/common/experimental_features.ts b/x-pack/plugins/fleet/common/experimental_features.ts index 48e77dbe1988d..a12711012f307 100644 --- a/x-pack/plugins/fleet/common/experimental_features.ts +++ b/x-pack/plugins/fleet/common/experimental_features.ts @@ -29,6 +29,7 @@ export const allowedExperimentalValues = Object.freeze>( enableStrictKQLValidation: false, subfeaturePrivileges: false, enablePackagesStateMachine: true, + advancedPolicySettings: true, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 1ed749fe31dea..d10af2053ef4e 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -7383,6 +7383,11 @@ "type": "object", "description": "Override settings that are defined in the agent policy. Input settings cannot be overridden. The override option should be used only in unusual circumstances and not as a routine procedure.", "nullable": true + }, + "advanced_settings": { + "type": "object", + "description": "Advanced settings stored in the agent policy, e.g. agent_limits_go_max_procs", + "nullable": true } }, "required": [ diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 03bb90fd84d73..25850c8c6d811 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -4734,6 +4734,12 @@ components: settings cannot be overridden. The override option should be used only in unusual circumstances and not as a routine procedure. nullable: true + advanced_settings: + type: object + description: >- + Advanced settings stored in the agent policy, e.g. + agent_limits_go_max_procs + nullable: true required: - id - status diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml index 8b088e34c10dc..070d72a5ec353 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_policy.yaml @@ -69,6 +69,10 @@ properties: type: object description: Override settings that are defined in the agent policy. Input settings cannot be overridden. The override option should be used only in unusual circumstances and not as a routine procedure. nullable: true + advanced_settings: + type: object + description: Advanced settings stored in the agent policy, e.g. agent_limits_go_max_procs + nullable: true required: - id - status diff --git a/x-pack/plugins/fleet/common/settings/agent_policy_settings.ts b/x-pack/plugins/fleet/common/settings/agent_policy_settings.ts new file mode 100644 index 0000000000000..7b170d330e650 --- /dev/null +++ b/x-pack/plugins/fleet/common/settings/agent_policy_settings.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { z } from 'zod'; + +import type { SettingsConfig } from './types'; + +export const zodStringWithDurationValidation = z + .string() + .refine((val) => val.match(/^(\d+[s|m|h|d])?$/), { + message: i18n.translate( + 'xpack.fleet.settings.agentPolicyAdvanced.downloadTimeoutValidationMessage', + { + defaultMessage: 'Must be a string with a time unit, e.g. 30s, 5m, 2h, 1d', + } + ), + }); + +export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ + { + name: 'agent.limits.go_max_procs', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.goMaxProcsTitle', { + defaultMessage: 'GO_MAX_PROCS', + }), + description: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.goMaxProcsDescription', { + defaultMessage: 'Limits the maximum number of CPUs that can be executing simultaneously', + }), + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/enable-custom-policy-settings.html#limit-cpu-usage', + api_field: { + name: 'agent_limits_go_max_procs', + }, + schema: z.number().int().min(0).default(0), + }, + { + name: 'agent.download.timeout', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.downloadTimeoutTitle', { + defaultMessage: 'Agent binary download timeout', + }), + description: i18n.translate( + 'xpack.fleet.settings.agentPolicyAdvanced.downloadTimeoutDescription', + { + defaultMessage: 'Timeout in seconds for downloading the agent binary', + } + ), + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/enable-custom-policy-settings.html#configure-agent-download-timeout', + api_field: { + name: 'agent_download_timeout', + }, + schema: zodStringWithDurationValidation.default('120s'), + }, + { + name: 'agent.download.target_directory', + api_field: { + name: 'agent_download_target_directory', + }, + title: i18n.translate( + 'xpack.fleet.settings.agentPolicyAdvanced.agentDownloadTargetDirectoryTitle', + { + defaultMessage: 'Agent binary target directory', + } + ), + description: i18n.translate( + 'xpack.fleet.settings.agentPolicyAdvanced.agentDownloadTargetDirectoryDescription', + { + defaultMessage: 'The disk path to which the agent binary will be downloaded', + } + ), + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-download.html', + schema: z.string(), + }, + { + name: 'agent.logging.metrics.period', + api_field: { + name: 'agent_logging_metrics_period', + }, + title: i18n.translate( + 'xpack.fleet.settings.agentPolicyAdvanced.agentLoggingMetricsPeriodTitle', + { + defaultMessage: 'Agent logging metrics period', + } + ), + description: i18n.translate( + 'xpack.fleet.settings.agentPolicyAdvanced.agentLoggingMetricsPeriodDescription', + { + defaultMessage: 'The frequency of agent metrics logging', + } + ), + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: zodStringWithDurationValidation.default('30s'), + }, +]; diff --git a/x-pack/plugins/fleet/common/settings/index.ts b/x-pack/plugins/fleet/common/settings/index.ts new file mode 100644 index 0000000000000..b33370b593971 --- /dev/null +++ b/x-pack/plugins/fleet/common/settings/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { AGENT_POLICY_ADVANCED_SETTINGS } from './agent_policy_settings'; diff --git a/x-pack/plugins/fleet/common/settings/types.ts b/x-pack/plugins/fleet/common/settings/types.ts new file mode 100644 index 0000000000000..0fc8ad98f17c0 --- /dev/null +++ b/x-pack/plugins/fleet/common/settings/types.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { z } from 'zod'; + +export type SettingsSection = 'AGENT_POLICY_ADVANCED_SETTINGS'; + +export interface SettingsConfig { + name: string; + title: string; + description: string; + learnMoreLink?: string; + schema: z.ZodTypeAny; + api_field: { + name: string; + }; +} diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index 4e5d6250208a5..60e635f73f6cc 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -39,6 +39,7 @@ export interface NewAgentPolicy { agent_features?: Array<{ name: string; enabled: boolean }>; is_protected?: boolean; overrides?: { [key: string]: any } | null; + advanced_settings?: { [key: string]: any } | null; } // SO definition for this type is declared in server/types/interfaces diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx new file mode 100644 index 0000000000000..7d0cb7dbae341 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx @@ -0,0 +1,104 @@ +/* + * 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 { act, fireEvent } from '@testing-library/react'; + +import React from 'react'; + +import { z } from 'zod'; + +import { zodStringWithDurationValidation } from '../../../../../common/settings/agent_policy_settings'; +import type { SettingsConfig } from '../../../../../common/settings/types'; +import { createFleetTestRendererMock } from '../../../../mock'; + +import { ConfiguredSettings } from '.'; + +const mockUpdateAgentPolicy = jest.fn(); +const mockUpdateAdvancedSettingsHasErrors = jest.fn(); + +jest.mock('../../sections/agent_policy/components/agent_policy_form', () => ({ + useAgentPolicyFormContext: () => ({ + updateAdvancedSettingsHasErrors: mockUpdateAdvancedSettingsHasErrors, + updateAgentPolicy: mockUpdateAgentPolicy, + agentPolicy: { + advanced_settings: { + agent_limits_go_max_procs: 0, + agent_download_timeout: '120s', + }, + }, + }), +})); + +describe('ConfiguredSettings', () => { + const testRenderer = createFleetTestRendererMock(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + function render(settingsConfig: SettingsConfig[]) { + return testRenderer.render(); + } + + it('should render number field', () => { + const result = render([ + { + name: 'agent.limits.go_max_procs', + title: 'GO_MAX_PROCS', + description: 'Description', + learnMoreLink: '', + api_field: { + name: 'agent_limits_go_max_procs', + }, + schema: z.number().int().min(0).default(0), + }, + ]); + + expect(result.getByText('GO_MAX_PROCS')).not.toBeNull(); + const input = result.getByTestId('configuredSetting-agent.limits.go_max_procs'); + expect(input).toHaveValue(0); + + act(() => { + fireEvent.change(input, { target: { value: '1' } }); + }); + + expect(mockUpdateAgentPolicy).toHaveBeenCalledWith( + expect.objectContaining({ + advanced_settings: expect.objectContaining({ agent_limits_go_max_procs: 1 }), + }) + ); + }); + + it('should render string field with time duration validation', () => { + const result = render([ + { + name: 'agent.download.timeout', + title: 'Agent binary download timeout', + description: 'Description', + learnMoreLink: '', + api_field: { + name: 'agent_download_timeout', + }, + schema: zodStringWithDurationValidation.default('120s'), + }, + ]); + + expect(result.getByText('Agent binary download timeout')).not.toBeNull(); + const input = result.getByTestId('configuredSetting-agent.download.timeout'); + expect(input).toHaveValue('120s'); + + act(() => { + fireEvent.change(input, { target: { value: '120' } }); + }); + + expect(input).toHaveAttribute('aria-invalid', 'true'); + expect( + result.getByText('Must be a string with a time unit, e.g. 30s, 5m, 2h, 1d') + ).not.toBeNull(); + expect(mockUpdateAdvancedSettingsHasErrors).toHaveBeenCalledWith(true); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx new file mode 100644 index 0000000000000..03d66d23615ba --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx @@ -0,0 +1,157 @@ +/* + * 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 { z, ZodFirstPartyTypeKind } from 'zod'; +import React, { useState } from 'react'; +import { + EuiDescribedFormGroup, + EuiFieldNumber, + EuiFieldText, + EuiFormRow, + EuiLink, +} from '@elastic/eui'; + +import type { SettingsConfig } from '../../../../../common/settings/types'; +import { useAgentPolicyFormContext } from '../../sections/agent_policy/components/agent_policy_form'; + +export const settingComponentRegistry = new Map< + string, + (settingsconfig: SettingsConfig) => React.ReactElement +>(); + +settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodNumber, (settingsConfig) => { + return ( + ( + + )} + /> + ); +}); + +settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodString, (settingsConfig) => { + return ( + ( + + )} + /> + ); +}); + +const SettingsFieldWrapper: React.FC<{ + settingsConfig: SettingsConfig; + typeName: keyof typeof ZodFirstPartyTypeKind; + renderItem: Function; +}> = ({ settingsConfig, typeName, renderItem }) => { + const [error, setError] = useState(''); + const agentPolicyFormContext = useAgentPolicyFormContext(); + + const fieldKey = `configuredSetting-${settingsConfig.name}`; + const defaultValue: number = + settingsConfig.schema instanceof z.ZodDefault + ? settingsConfig.schema._def.defaultValue() + : undefined; + const coercedSchema = settingsConfig.schema as z.ZodString; + + const convertValue = (value: string, type: keyof typeof ZodFirstPartyTypeKind): any => { + if (type === ZodFirstPartyTypeKind.ZodNumber) { + if (value === '') { + return 0; + } + return parseInt(value, 10); + } + return value; + }; + + const handleChange = (e: React.ChangeEvent) => { + const newValue = convertValue(e.target.value, typeName); + const validationResults = coercedSchema.safeParse(newValue); + + if (!validationResults.success) { + setError(validationResults.error.issues[0].message); + agentPolicyFormContext?.updateAdvancedSettingsHasErrors(true); + } else { + setError(''); + agentPolicyFormContext?.updateAdvancedSettingsHasErrors(false); + } + + const newAdvancedSettings = { + ...(agentPolicyFormContext?.agentPolicy.advanced_settings ?? {}), + [settingsConfig.api_field.name]: newValue, + }; + + agentPolicyFormContext?.updateAgentPolicy({ advanced_settings: newAdvancedSettings }); + }; + + const fieldValue = + agentPolicyFormContext?.agentPolicy.advanced_settings?.[settingsConfig.api_field.name] ?? + defaultValue; + + return ( + {settingsConfig.title}} + description={ + <> + {settingsConfig.description}.{' '} + + Learn more. + + + } + > + + {renderItem({ fieldValue, handleChange, isInvalid: !!error, fieldKey, coercedSchema })} + + + ); +}; + +export function ConfiguredSettings({ + configuredSettings, +}: { + configuredSettings: SettingsConfig[]; +}) { + return ( + <> + {configuredSettings.map((configuredSetting) => { + const Component = settingComponentRegistry.get( + configuredSetting.schema instanceof z.ZodDefault + ? configuredSetting.schema._def.innerType._def.typeName === 'ZodEffects' + ? configuredSetting.schema._def.innerType._def.schema._def.typeName + : configuredSetting.schema._def.innerType._def.typeName + : configuredSetting.schema._def.typeName + ); + + if (!Component) { + throw new Error(`Unknown setting type: ${configuredSetting.schema._type}}`); + } + + return ; + })} + + ); +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx index 230a4152cf55d..8a0d92b159207 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx @@ -12,13 +12,19 @@ import { EuiForm, EuiHorizontalRule, EuiSpacer, + EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; +import { AGENT_POLICY_ADVANCED_SETTINGS } from '../../../../../../common/settings'; import type { NewAgentPolicy, AgentPolicy } from '../../../types'; import { useAuthz } from '../../../../../hooks'; +import { ConfiguredSettings } from '../../../components/form_settings'; + +import { ExperimentalFeaturesService } from '../../../../../services'; + import { AgentPolicyAdvancedOptionsContent } from './agent_policy_advanced_fields'; import { AgentPolicyGeneralFields } from './agent_policy_general_fields'; import { AgentPolicyFormSystemMonitoringCheckbox } from './agent_policy_system_monitoring_field'; @@ -37,7 +43,21 @@ interface Props { updateSysMonitoring: (newValue: boolean) => void; validation: ValidationResults; isEditing?: boolean; + // form error state is passed up to the form + updateAdvancedSettingsHasErrors: (hasErrors: boolean) => void; } +const AgentPolicyFormContext = React.createContext< + | { + agentPolicy: Partial & { [key: string]: any }; + updateAgentPolicy: (u: Partial) => void; + updateAdvancedSettingsHasErrors: (hasErrors: boolean) => void; + } + | undefined +>(undefined); + +export const useAgentPolicyFormContext = () => { + return React.useContext(AgentPolicyFormContext); +}; export const AgentPolicyForm: React.FunctionComponent = ({ agentPolicy, @@ -46,10 +66,13 @@ export const AgentPolicyForm: React.FunctionComponent = ({ updateSysMonitoring, validation, isEditing = false, + updateAdvancedSettingsHasErrors, }) => { const authz = useAuthz(); const disabled = !authz.fleet.allAgents; + const { advancedPolicySettings } = ExperimentalFeaturesService.get(); + const generalSettingsWrapper = (children: JSX.Element[]) => ( = ({ ); return ( - - {!isEditing ? ( - - ) : ( - generalSettingsWrapper([ + + + {!isEditing ? ( , - ]) - )} - {!isEditing ? ( - - ) : null} - {!isEditing ? ( - <> - - - + ) : ( + generalSettingsWrapper([ + , + ]) + )} + {!isEditing ? ( + + ) : null} + {!isEditing ? ( + <> + + + + } + buttonClassName="ingest-active-button" + > + + - } - buttonClassName="ingest-active-button" - > - + + {advancedPolicySettings ? ( + <> + + + +

+ +

+
+ + + + ) : null} +
+ + ) : ( + <> -
- - ) : ( - - )} -
+ {advancedPolicySettings ? ( + <> + + + +

+ +

+
+ + + + ) : null} + + + )} +
+ ); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx index b3559fb876e1f..f9ca5fa24226c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/settings/index.tsx @@ -52,6 +52,7 @@ const pickAgentPolicyKeysToSend = (agentPolicy: AgentPolicy) => 'fleet_server_host_id', 'agent_features', 'is_protected', + 'advanced_settings', ]); const FormWrapper = styled.div` @@ -77,6 +78,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( const [agentCount, setAgentCount] = useState(0); const [withSysMonitoring, setWithSysMonitoring] = useState(true); const validation = agentPolicyFormValidation(agentPolicy); + const [hasAdvancedSettingsErrors, setHasAdvancedSettingsErrors] = useState(false); const updateAgentPolicy = (updatedFields: Partial) => { setAgentPolicy({ @@ -169,6 +171,7 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( updateSysMonitoring={(newValue) => setWithSysMonitoring(newValue)} validation={validation} isEditing={true} + updateAdvancedSettingsHasErrors={setHasAdvancedSettingsErrors} /> {hasChanges ? ( @@ -202,7 +205,11 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( {showDevtoolsRequest ? ( 0} + isDisabled={ + isLoading || + Object.keys(validation).length > 0 || + hasAdvancedSettingsErrors + } btnProps={{ color: 'text', }} @@ -221,7 +228,10 @@ export const SettingsView = memo<{ agentPolicy: AgentPolicy }>( onClick={onSubmit} isLoading={isLoading} isDisabled={ - !hasFleetAllPrivileges || isLoading || Object.keys(validation).length > 0 + !hasFleetAllPrivileges || + isLoading || + Object.keys(validation).length > 0 || + hasAdvancedSettingsErrors } iconType="save" color="primary" diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx index 09650bafdff3c..bfe9d233b2bf4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/list_page/components/create_agent_policy.tsx @@ -53,6 +53,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ const [isLoading, setIsLoading] = useState(false); const [withSysMonitoring, setWithSysMonitoring] = useState(true); const validation = agentPolicyFormValidation(agentPolicy); + const [hasAdvancedSettingsErrors, setHasAdvancedSettingsErrors] = useState(false); const updateAgentPolicy = (updatedFields: Partial) => { setAgentPolicy({ @@ -95,6 +96,7 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ withSysMonitoring={withSysMonitoring} updateSysMonitoring={(newValue) => setWithSysMonitoring(newValue)} validation={validation} + updateAdvancedSettingsHasErrors={setHasAdvancedSettingsErrors} /> ); @@ -120,7 +122,9 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ {showDevtoolsRequest ? ( 0} + isDisabled={ + isLoading || Object.keys(validation).length > 0 || hasAdvancedSettingsErrors + } description={i18n.translate( 'xpack.fleet.createAgentPolicy.devtoolsRequestDescription', { @@ -136,7 +140,10 @@ export const CreateAgentPolicyFlyout: React.FunctionComponent = ({ fill isLoading={isLoading} isDisabled={ - !hasFleetAllPrivileges || isLoading || Object.keys(validation).length > 0 + !hasFleetAllPrivileges || + isLoading || + Object.keys(validation).length > 0 || + hasAdvancedSettingsErrors } onClick={async () => { setIsLoading(true); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_authentication.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_authentication.tsx index 5d21567cbbb35..199b92d4de98e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_authentication.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_kafka_authentication.tsx @@ -75,6 +75,10 @@ export const OutputFormKafkaAuthentication: React.FunctionComponent<{ onToggleSecretStorage: (secretEnabled: boolean) => void; }> = (props) => { const { inputs, useSecretsStorage, onToggleSecretStorage } = props; + const [isConvertedToSecret, setIsConvertedToSecret] = React.useState({ + kafkaAuthPassword: false, + kafkaSslKey: false, + }); const [isFirstLoad, setIsFirstLoad] = React.useState(true); useEffect(() => { @@ -85,11 +89,13 @@ export const OutputFormKafkaAuthentication: React.FunctionComponent<{ if (inputs.kafkaAuthPasswordInput.value && !inputs.kafkaAuthPasswordSecretInput.value) { inputs.kafkaAuthPasswordSecretInput.setValue(inputs.kafkaAuthPasswordInput.value); inputs.kafkaAuthPasswordInput.clear(); + setIsConvertedToSecret({ ...isConvertedToSecret, kafkaAuthPassword: true }); } if (inputs.kafkaSslKeyInput.value && !inputs.kafkaSslKeySecretInput.value) { inputs.kafkaSslKeySecretInput.setValue(inputs.kafkaSslKeyInput.value); inputs.kafkaSslKeyInput.clear(); + setIsConvertedToSecret({ ...isConvertedToSecret, kafkaSslKey: true }); } } }, [ @@ -100,6 +106,7 @@ export const OutputFormKafkaAuthentication: React.FunctionComponent<{ inputs.kafkaSslKeySecretInput, isFirstLoad, setIsFirstLoad, + isConvertedToSecret, ]); const onToggleSecretAndClearValue = (secretEnabled: boolean) => { @@ -110,6 +117,7 @@ export const OutputFormKafkaAuthentication: React.FunctionComponent<{ inputs.kafkaAuthPasswordSecretInput.setValue(''); inputs.kafkaSslKeySecretInput.setValue(''); } + setIsConvertedToSecret({ kafkaAuthPassword: false, kafkaSslKey: false }); onToggleSecretStorage(secretEnabled); }; @@ -221,6 +229,7 @@ export const OutputFormKafkaAuthentication: React.FunctionComponent<{ )} {...inputs.kafkaSslKeySecretInput.formRowProps} useSecretsStorage={useSecretsStorage} + isConvertedToSecret={isConvertedToSecret.kafkaSslKey} onToggleSecretStorage={onToggleSecretAndClearValue} cancelEdit={inputs.kafkaSslKeySecretInput.cancelEdit} > @@ -291,6 +300,7 @@ export const OutputFormKafkaAuthentication: React.FunctionComponent<{ )} {...inputs.kafkaAuthPasswordSecretInput.formRowProps} useSecretsStorage={useSecretsStorage} + isConvertedToSecret={isConvertedToSecret.kafkaAuthPassword} onToggleSecretStorage={onToggleSecretAndClearValue} cancelEdit={inputs.kafkaAuthPasswordSecretInput.cancelEdit} > diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_logstash.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_logstash.tsx index 9277ee7290937..1884c5aa7551f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_logstash.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_logstash.tsx @@ -32,6 +32,9 @@ export const OutputFormLogstashSection: React.FunctionComponent = (props) const { docLinks } = useStartServices(); const [isFirstLoad, setIsFirstLoad] = React.useState(true); + const [isConvertedToSecret, setIsConvertedToSecret] = React.useState({ + sslKey: false, + }); useEffect(() => { if (!isFirstLoad) return; @@ -41,6 +44,7 @@ export const OutputFormLogstashSection: React.FunctionComponent = (props) if (inputs.sslKeyInput.value && !inputs.sslKeySecretInput.value) { inputs.sslKeySecretInput.setValue(inputs.sslKeyInput.value); inputs.sslKeyInput.clear(); + setIsConvertedToSecret({ ...isConvertedToSecret, sslKey: true }); } } }, [ @@ -49,6 +53,7 @@ export const OutputFormLogstashSection: React.FunctionComponent = (props) inputs.sslKeySecretInput, isFirstLoad, setIsFirstLoad, + isConvertedToSecret, ]); const onToggleSecretAndClearValue = (secretEnabled: boolean) => { @@ -57,6 +62,7 @@ export const OutputFormLogstashSection: React.FunctionComponent = (props) } else { inputs.sslKeySecretInput.setValue(''); } + setIsConvertedToSecret({ sslKey: false }); onToggleSecretStorage(secretEnabled); }; @@ -172,6 +178,7 @@ export const OutputFormLogstashSection: React.FunctionComponent = (props) })} {...inputs.sslKeySecretInput.formRowProps} useSecretsStorage={useSecretsStorage} + isConvertedToSecret={isConvertedToSecret.sslKey} onToggleSecretStorage={onToggleSecretAndClearValue} cancelEdit={inputs.sslKeySecretInput.cancelEdit} > diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_remote_es.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_remote_es.tsx index 1b44bdc97ed8f..354c6ce7d3821 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_remote_es.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_remote_es.tsx @@ -23,6 +23,9 @@ interface Props { export const OutputFormRemoteEsSection: React.FunctionComponent = (props) => { const { inputs, useSecretsStorage, onToggleSecretStorage } = props; + const [isConvertedToSecret, setIsConvertedToSecret] = React.useState({ + serviceToken: false, + }); const [isFirstLoad, setIsFirstLoad] = React.useState(true); @@ -34,6 +37,7 @@ export const OutputFormRemoteEsSection: React.FunctionComponent = (props) if (inputs.serviceTokenInput.value && !inputs.serviceTokenSecretInput.value) { inputs.serviceTokenSecretInput.setValue(inputs.serviceTokenInput.value); inputs.serviceTokenInput.clear(); + setIsConvertedToSecret({ serviceToken: true }); } } }, [ @@ -42,6 +46,7 @@ export const OutputFormRemoteEsSection: React.FunctionComponent = (props) inputs.serviceTokenSecretInput, isFirstLoad, setIsFirstLoad, + isConvertedToSecret, ]); const onToggleSecretAndClearValue = (secretEnabled: boolean) => { @@ -50,6 +55,7 @@ export const OutputFormRemoteEsSection: React.FunctionComponent = (props) } else { inputs.serviceTokenSecretInput.setValue(''); } + setIsConvertedToSecret({ ...isConvertedToSecret, serviceToken: false }); onToggleSecretStorage(secretEnabled); }; @@ -104,6 +110,7 @@ export const OutputFormRemoteEsSection: React.FunctionComponent = (props) {...inputs.serviceTokenSecretInput.formRowProps} cancelEdit={inputs.serviceTokenSecretInput.cancelEdit} useSecretsStorage={useSecretsStorage} + isConvertedToSecret={isConvertedToSecret.serviceToken} onToggleSecretStorage={onToggleSecretAndClearValue} > { expect(onToggleSecretStorage).toHaveBeenCalledWith(true); }); + + it('should display input normally and display a callout when the field is converted to secret storage', () => { + const { getByText, queryByText } = render( + + + + ); + + expect(queryByText('Replace Test Secret')).not.toBeInTheDocument(); + expect( + getByText('This field will be re-saved using secret storage from plain text storage.', { + exact: false, + }) + ).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx index 2028c6107bfd3..3b39ecc4dfa2b 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/output_form_secret_form_row.tsx @@ -7,16 +7,18 @@ import { EuiButtonEmpty, + EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIcon, + EuiLink, EuiPanel, EuiSpacer, EuiText, EuiToolTip, } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -24,6 +26,7 @@ export const SecretFormRow: React.FC<{ fullWidth?: boolean; children: ConstructorParameters[0]['children']; useSecretsStorage: boolean; + isConvertedToSecret?: boolean; onToggleSecretStorage: (secretEnabled: boolean) => void; error?: string[]; isInvalid?: boolean; @@ -43,10 +46,11 @@ export const SecretFormRow: React.FC<{ onToggleSecretStorage, cancelEdit, useSecretsStorage, + isConvertedToSecret = false, label, }) => { const hasInitialValue = !!initialValue; - const [editMode, setEditMode] = useState(!initialValue); + const [editMode, setEditMode] = useState(isConvertedToSecret || !initialValue); const valueHiddenPanel = ( @@ -101,7 +105,7 @@ export const SecretFormRow: React.FC<{ const editValue = ( <> {children} - {hasInitialValue && ( + {hasInitialValue && !isConvertedToSecret && ( {cancelButton} @@ -110,7 +114,7 @@ export const SecretFormRow: React.FC<{ ); const secretLabel = ( - + <>   {title} @@ -123,25 +127,49 @@ export const SecretFormRow: React.FC<{ > - + ); - const helpText = !initialValue ? ( - onToggleSecretStorage(false)} color="primary" size="xs"> - - - ), - }} - /> - ) : undefined; + const helpText = useMemo(() => { + if (isConvertedToSecret) + return ( + + onToggleSecretStorage(false)} color="primary"> + + + ), + }} + /> + + ); + + if (!initialValue) + return ( + onToggleSecretStorage(false)} color="primary"> + + + ), + }} + /> + ); + return undefined; + }, [initialValue, isConvertedToSecret, onToggleSecretStorage]); const plainTextHelp = ( onToggleSecretStorage(true)} color="primary" size="xs"> + onToggleSecretStorage(true)} color="primary"> - + ), }} /> diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 4aef23990ffec..363b26be84a47 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -156,6 +156,7 @@ export const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ is_protected: { type: 'boolean' }, overrides: { type: 'flattened', index: false }, keep_monitoring_alive: { type: 'boolean' }, + advanced_settings: { type: 'flattened', index: false }, }, }, migrations: { @@ -165,6 +166,18 @@ export const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({ '8.5.0': migrateAgentPolicyToV850, '8.9.0': migrateAgentPolicyToV890, }, + modelVersions: { + '1': { + changes: [ + { + type: 'mappings_addition', + addedMappings: { + advanced_settings: { type: 'flattened', index: false }, + }, + }, + ], + }, + }, }, [OUTPUT_SAVED_OBJECT_TYPE]: { name: OUTPUT_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index 0ecd385c2516c..7f203dd139954 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -714,6 +714,30 @@ describe('getFullAgentPolicy', () => { revision: 1, }); }); + + it('should return a policy with advanced settings', async () => { + mockAgentPolicy({ + advanced_settings: { + agent_limits_go_max_procs: 2, + agent_download_timeout: '60s', + agent_download_target_directory: '/tmp', + agent_logging_metrics_period: '10s', + }, + }); + const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(agentPolicy).toMatchObject({ + id: 'agent-policy', + agent: { + download: { + timeout: '60s', + target_directory: '/tmp', + }, + limits: { go_max_procs: 2 }, + logging: { metrics: { period: '10s' } }, + }, + }); + }); }); describe('transformOutputToFullPolicyOutput', () => { diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts index ab277a97774d0..bec47eea524c4 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -10,6 +10,7 @@ import type { SavedObjectsClientContract } from '@kbn/core/server'; import { safeLoad } from 'js-yaml'; import deepMerge from 'deepmerge'; +import { set } from '@kbn/safer-lodash-set'; import { getDefaultPresetForEsOutput, @@ -37,7 +38,7 @@ import { kafkaCompressionType, outputType, } from '../../../common/constants'; - +import { getSettingsValuesForAgentPolicy } from '../form_settings'; import { getPackageInfo } from '../epm/packages'; import { pkgToPkgKey, splitPkgKey } from '../epm/registry'; import { appContextService } from '../app_context'; @@ -242,6 +243,14 @@ export async function getFullAgentPolicy( fullAgentPolicy.fleet = generateFleetConfig(fleetServerHosts, proxies); } + const settingsValues = getSettingsValuesForAgentPolicy( + 'AGENT_POLICY_ADVANCED_SETTINGS', + agentPolicy + ); + Object.entries(settingsValues).forEach(([settingsKey, settingValue]) => { + set(fullAgentPolicy, settingsKey, settingValue); + }); + // populate protection and signed properties const messageSigningService = appContextService.getMessageSigningService(); if (messageSigningService && fullAgentPolicy.agent) { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_create_restart_installation.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_create_restart_installation.ts index 58daa6c379134..af0fb0fea9caf 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_create_restart_installation.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_create_restart_installation.ts @@ -11,6 +11,7 @@ import { MAX_TIME_COMPLETE_INSTALL } from '../../../../../constants'; import { restartInstallation, createInstallation } from '../../install'; import type { InstallContext } from '../_state_machine_package_install'; +import { withPackageSpan } from '../../utils'; export async function stepCreateRestartInstallation(context: InstallContext) { const { @@ -42,13 +43,15 @@ export async function stepCreateRestartInstallation(context: InstallContext) { if (force) { logger.debug(`Package install - Forced installation, restarting`); - await restartInstallation({ - savedObjectsClient, - pkgName, - pkgVersion, - installSource, - verificationResult, - }); + await withPackageSpan('Restarting installation with force flag', () => + restartInstallation({ + savedObjectsClient, + pkgName, + pkgVersion, + installSource, + verificationResult, + }) + ); } else { throw new ConcurrentInstallOperationError( `Concurrent installation or upgrade of ${pkgName || 'unknown'}-${ @@ -62,23 +65,27 @@ export async function stepCreateRestartInstallation(context: InstallContext) { logger.debug( `Package install - no installation running or the installation has been running longer than ${MAX_TIME_COMPLETE_INSTALL}, restarting` ); - await restartInstallation({ - savedObjectsClient, - pkgName, - pkgVersion, - installSource, - verificationResult, - }); + await withPackageSpan('Restarting installation', () => + restartInstallation({ + savedObjectsClient, + pkgName, + pkgVersion, + installSource, + verificationResult, + }) + ); } } else { logger.debug(`Package install - Create installation`); - await createInstallation({ - savedObjectsClient, - packageInfo, - installSource, - spaceId, - verificationResult, - }); + await withPackageSpan('Creating installation', () => + createInstallation({ + savedObjectsClient, + packageInfo, + installSource, + spaceId, + verificationResult, + }) + ); } } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_delete_previous_pipelines.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_delete_previous_pipelines.ts index eb80ef16dbcb0..4a524e3e07939 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_delete_previous_pipelines.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_delete_previous_pipelines.ts @@ -36,26 +36,30 @@ export async function stepDeletePreviousPipelines(context: InstallContext) { installedPkg ) { logger.debug(`Package install - installType ${installType} Deleting previous ingest pipelines`); - updatedESReferences = await withPackageSpan('Delete previous ingest pipelines', () => - deletePreviousPipelines( - esClient, - savedObjectsClient, - pkgName, - installedPkg!.attributes.version, - esReferences || [] - ) + updatedESReferences = await withPackageSpan( + 'Delete previous ingest pipelines with installType update or reupdate', + () => + deletePreviousPipelines( + esClient, + savedObjectsClient, + pkgName, + installedPkg!.attributes.version, + esReferences || [] + ) ); } else if (installType === 'rollback' && installedPkg) { // pipelines from a different version may have been installed during a failed update logger.debug(`Package install - installType ${installType} Deleting previous ingest pipelines`); - updatedESReferences = await withPackageSpan('Delete previous ingest pipelines', () => - deletePreviousPipelines( - esClient, - savedObjectsClient, - pkgName, - installedPkg!.attributes.install_version, - esReferences || [] - ) + updatedESReferences = await withPackageSpan( + 'Delete previous ingest pipelines with installType rollback', + () => + deletePreviousPipelines( + esClient, + savedObjectsClient, + pkgName, + installedPkg!.attributes.install_version, + esReferences || [] + ) ); } else { // if none of the previous cases, return the original esReferences diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_index_template_pipelines.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_index_template_pipelines.ts index e2b6918b722cf..18c0956da31c9 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_index_template_pipelines.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_install_index_template_pipelines.ts @@ -10,6 +10,7 @@ import { getNormalizedDataStreams } from '../../../../../../common/services'; import { installIndexTemplatesAndPipelines } from '../../install_index_template_pipeline'; import type { InstallContext } from '../_state_machine_package_install'; +import { withPackageSpan } from '../../utils'; export async function stepInstallIndexTemplatePipelines(context: InstallContext) { const { esClient, savedObjectsClient, packageInstallContext, logger, installedPkg } = context; @@ -20,15 +21,18 @@ export async function stepInstallIndexTemplatePipelines(context: InstallContext) logger.debug( `Package install - Installing index templates and pipelines, packageInfo.type: ${packageInfo.type}` ); - const { installedTemplates, esReferences: templateEsReferences } = - await installIndexTemplatesAndPipelines({ - installedPkg: installedPkg ? installedPkg.attributes : undefined, - packageInstallContext, - esClient, - savedObjectsClient, - logger, - esReferences, - }); + const { installedTemplates, esReferences: templateEsReferences } = await withPackageSpan( + 'Install index templates and pipelines with packageInfo integration', + () => + installIndexTemplatesAndPipelines({ + installedPkg: installedPkg ? installedPkg.attributes : undefined, + packageInstallContext, + esClient, + savedObjectsClient, + logger, + esReferences, + }) + ); return { esReferences: templateEsReferences, indexTemplates: installedTemplates, @@ -50,16 +54,19 @@ export async function stepInstallIndexTemplatePipelines(context: InstallContext) ); if (dataStreams.length) { - const { installedTemplates, esReferences: templateEsReferences } = - await installIndexTemplatesAndPipelines({ - installedPkg: installedPkg ? installedPkg.attributes : undefined, - packageInstallContext, - esClient, - savedObjectsClient, - logger, - esReferences, - onlyForDataStreams: dataStreams, - }); + const { installedTemplates, esReferences: templateEsReferences } = await withPackageSpan( + 'Install index templates and pipelines with packageInfo input', + () => + installIndexTemplatesAndPipelines({ + installedPkg: installedPkg ? installedPkg.attributes : undefined, + packageInstallContext, + esClient, + savedObjectsClient, + logger, + esReferences, + onlyForDataStreams: dataStreams, + }) + ); return { esReferences: templateEsReferences, indexTemplates: installedTemplates }; } } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_remove_legacy_templates.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_remove_legacy_templates.ts index 0c70989a67096..b93197af5e521 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_remove_legacy_templates.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/step_remove_legacy_templates.ts @@ -8,12 +8,15 @@ import { removeLegacyTemplates } from '../../../elasticsearch/template/remove_legacy'; import type { InstallContext } from '../_state_machine_package_install'; +import { withPackageSpan } from '../../utils'; export async function stepRemoveLegacyTemplates(context: InstallContext) { const { esClient, packageInstallContext, logger } = context; const { packageInfo } = packageInstallContext; try { - await removeLegacyTemplates({ packageInfo, esClient, logger }); + await withPackageSpan('Remove legacy templates', () => + removeLegacyTemplates({ packageInfo, esClient, logger }) + ); } catch (e) { logger.warn(`Error removing legacy templates: ${e.message}`); } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/update_latest_executed_state.ts b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/update_latest_executed_state.ts index 55d7997ad58f7..39b2492d77994 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/update_latest_executed_state.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install_state_machine/steps/update_latest_executed_state.ts @@ -12,6 +12,7 @@ import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../../../constants'; import { auditLoggingService } from '../../../../audit_logging'; import type { InstallContext } from '../_state_machine_package_install'; +import { withPackageSpan } from '../../utils'; // Function invoked after each transition export const updateLatestExecutedState = async (context: InstallContext) => { @@ -28,9 +29,11 @@ export const updateLatestExecutedState = async (context: InstallContext) => { id: pkgName, savedObjectType: PACKAGES_SAVED_OBJECT_TYPE, }); - return await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { - latest_executed_state: latestExecutedState, - }); + return await withPackageSpan('Update latest executed state', () => + savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { + latest_executed_state: latestExecutedState, + }) + ); } catch (err) { if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { logger.error(`Failed to update SO with latest executed state: ${err}`); diff --git a/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts b/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts new file mode 100644 index 0000000000000..d620737e8dc27 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts @@ -0,0 +1,75 @@ +/* + * 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 { z } from 'zod'; +import { schema } from '@kbn/config-schema'; + +import type { SettingsConfig } from '../../../common/settings/types'; + +import { _getSettingsAPISchema, _getSettingsValuesForAgentPolicy } from './form_settings'; + +const TEST_SETTINGS: SettingsConfig[] = [ + { + name: 'test.foo', + title: 'test', + description: 'test', + schema: z.boolean(), + api_field: { + name: 'test_foo', + }, + }, + { + name: 'test.foo.default_value', + title: 'test', + description: 'test', + schema: z.string().default('test'), + api_field: { + name: 'test_foo_default_value', + }, + }, +]; + +describe('form_settings', () => { + describe('_getSettingsAPISchema', () => { + it('generate a valid API schema for api_field', () => { + const apiSchema = schema.object(_getSettingsAPISchema(TEST_SETTINGS)); + + expect(() => + apiSchema.validate({ + advanced_settings: { + test_foo: 'not valid', + }, + }) + ).toThrowError(/Expected boolean, received string/); + + expect(() => + apiSchema.validate({ + advanced_settings: { + test_foo: true, + }, + }) + ).not.toThrow(); + }); + + it('generate a valid API schema for api_field with default value', () => { + const apiSchema = schema.object(_getSettingsAPISchema(TEST_SETTINGS)); + const res = apiSchema.validate({ advanced_settings: {} }); + expect(res).toEqual({ advanced_settings: { test_foo_default_value: 'test' } }); + }); + }); + + describe('_getSettingsValuesForAgentPolicy', () => { + it('generate the proper values for agent policy (full agent policy)', () => { + const res = _getSettingsValuesForAgentPolicy(TEST_SETTINGS, { + advanced_settings: { + test_foo_default_value: 'test', + }, + } as any); + expect(res).toEqual({ 'test.foo.default_value': 'test' }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts b/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts new file mode 100644 index 0000000000000..a381fcd55b4ba --- /dev/null +++ b/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { type Props, schema } from '@kbn/config-schema'; +import { stringifyZodError } from '@kbn/zod-helpers'; + +import type { SettingsConfig, SettingsSection } from '../../../common/settings/types'; +import { AGENT_POLICY_ADVANCED_SETTINGS } from '../../../common/settings'; +import type { AgentPolicy } from '../../types'; + +export function getSettingsAPISchema(settingSection: SettingsSection) { + const settings = getSettings(settingSection); + + return _getSettingsAPISchema(settings); +} + +export function _getSettingsAPISchema(settings: SettingsConfig[]): Props { + const validations: Props = {}; + settings.forEach((setting) => { + if (!setting.api_field) { + return; + } + const defaultValueRes = setting.schema.safeParse(undefined); + const defaultValue = defaultValueRes.success ? defaultValueRes.data : undefined; + if (defaultValue) { + validations[setting.api_field.name] = schema.oneOf( + [ + schema.any({ + validate: (val: any) => { + const res = setting.schema.safeParse(val); + if (!res.success) { + return stringifyZodError(res.error); + } + }, + }), + schema.literal(null), + ], + { + defaultValue, + } + ); + } else { + validations[setting.api_field.name] = schema.maybe( + schema.nullable( + schema.any({ + validate: (val: any) => { + const res = setting.schema.safeParse(val); + if (!res.success) { + return stringifyZodError(res.error); + } + }, + }) + ) + ); + } + }); + + const advancedSettingsValidations: Props = { + advanced_settings: schema.maybe( + schema.object({ + ...validations, + }) + ), + }; + return advancedSettingsValidations; +} + +export function getSettingsValuesForAgentPolicy( + settingSection: SettingsSection, + agentPolicy: AgentPolicy +) { + const settings = getSettings(settingSection); + + return _getSettingsValuesForAgentPolicy(settings, agentPolicy); +} + +export function _getSettingsValuesForAgentPolicy( + settings: SettingsConfig[], + agentPolicy: AgentPolicy +) { + const settingsValues: { [k: string]: any } = {}; + settings.forEach((setting) => { + if (!setting.api_field) { + return; + } + + const val = agentPolicy.advanced_settings?.[setting.api_field.name]; + if (val) { + settingsValues[setting.name] = val; + } + }); + return settingsValues; +} + +export function getSettings(settingSection: SettingsSection) { + if (settingSection === 'AGENT_POLICY_ADVANCED_SETTINGS') { + return AGENT_POLICY_ADVANCED_SETTINGS; + } + + throw new Error(`Invalid settings section ${settingSection}`); +} diff --git a/x-pack/plugins/fleet/server/services/form_settings/index.ts b/x-pack/plugins/fleet/server/services/form_settings/index.ts new file mode 100644 index 0000000000000..b88cd1e0eda2a --- /dev/null +++ b/x-pack/plugins/fleet/server/services/form_settings/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { + getSettings, + getSettingsAPISchema, + getSettingsValuesForAgentPolicy, +} from './form_settings'; diff --git a/x-pack/plugins/fleet/server/types/models/agent_policy.ts b/x-pack/plugins/fleet/server/types/models/agent_policy.ts index 518510f0b8454..748a5c81c1bfb 100644 --- a/x-pack/plugins/fleet/server/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/agent_policy.ts @@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema'; import { agentPolicyStatuses, dataTypes } from '../../../common/constants'; import { isValidNamespace } from '../../../common/services'; +import { getSettingsAPISchema } from '../../services/form_settings'; import { PackagePolicySchema } from './package_policy'; @@ -81,6 +82,7 @@ export const AgentPolicyBaseSchema = { }) ) ), + ...getSettingsAPISchema('AGENT_POLICY_ADVANCED_SETTINGS'), }; export const NewAgentPolicySchema = schema.object({ diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index b99fb0cce0985..d57c36aa70165 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -105,5 +105,6 @@ "@kbn/core-http-server-mocks", "@kbn/code-editor", "@kbn/core-test-helpers-model-versions", + "@kbn/zod-helpers", ] } diff --git a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/flyout_wrapper.tsx b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/flyout_wrapper.tsx index f8ee1c5779693..3c013cdc95998 100644 --- a/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/flyout_wrapper.tsx +++ b/x-pack/plugins/lens/public/app_plugin/shared/edit_on_the_fly/flyout_wrapper.tsx @@ -62,18 +62,10 @@ export const FlyoutWrapper = ({ values: { lang: language }, })} ; diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/service_list/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/service_list/index.tsx index 6c849e97fcd3d..e42fd862dc058 100644 --- a/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/service_list/index.tsx +++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_inventory/service_list/index.tsx @@ -10,6 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, + EuiIconTip, EuiLink, EuiSpacer, EuiText, @@ -153,9 +154,27 @@ export function getServiceColumns({ ? [ { field: ServiceInventoryFieldName.HealthStatus, - name: i18n.translate('xpack.apm.servicesTable.healthColumnLabel', { - defaultMessage: 'Health', - }), + name: ( + <> + {i18n.translate('xpack.apm.servicesTable.healthColumnLabel', { + defaultMessage: 'Health', + })}{' '} + + + ), width: `${unit * 6}px`, sortable: true, render: (_, { healthStatus }) => { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/anomaly/get_service_group_fields_for_anomaly.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/anomaly/get_service_group_fields_for_anomaly.ts index cde1ba830b2cc..b07f4711bc965 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/anomaly/get_service_group_fields_for_anomaly.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/anomaly/get_service_group_fields_for_anomaly.ts @@ -19,8 +19,8 @@ import { import { alertingEsClient } from '../../alerting_es_client'; import { getApmAlertSourceFields, - getServiceGroupFieldsAgg, -} from '../get_service_group_fields'; + getApmAlertSourceFieldsAgg, +} from '../get_apm_alert_source_fields'; export async function getServiceGroupFieldsForAnomaly({ apmIndices, @@ -64,7 +64,7 @@ export async function getServiceGroupFieldsForAnomaly({ }, }, aggs: { - ...getServiceGroupFieldsAgg({ + ...getApmAlertSourceFieldsAgg({ sort: [{ [TRANSACTION_DURATION]: { order: 'desc' as const } }], }), }, @@ -75,6 +75,7 @@ export async function getServiceGroupFieldsForAnomaly({ scopedClusterClient, params, }); + if (!response.aggregations) { return {}; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts index 2a67764fb17b5..a29b0b5094285 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/error_count/register_error_count_rule_type.ts @@ -63,8 +63,8 @@ import { } from '../../register_apm_rule_types'; import { getApmAlertSourceFields, - getServiceGroupFieldsAgg, -} from '../get_service_group_fields'; + getApmAlertSourceFieldsAgg, +} from '../get_apm_alert_source_fields'; import { getGroupByTerms } from '../utils/get_groupby_terms'; import { getGroupByActionVariables } from '../utils/get_groupby_action_variables'; import { getAllGroupByFields } from '../../../../../common/rules/get_all_groupby_fields'; @@ -200,7 +200,7 @@ export function registerErrorCountRuleType({ size: 1000, order: { _count: 'desc' as const }, }, - aggs: getServiceGroupFieldsAgg(), + aggs: getApmAlertSourceFieldsAgg(), }, }, }, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/get_service_group_fields.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/get_apm_alert_source_fields.ts similarity index 85% rename from x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/get_service_group_fields.ts rename to x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/get_apm_alert_source_fields.ts index 97de147fa52f9..2cd0b70ecac8a 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/get_service_group_fields.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/get_apm_alert_source_fields.ts @@ -6,23 +6,16 @@ */ import { AggregationsTopHitsAggregation } from '@elastic/elasticsearch/lib/api/types'; -import { CONTAINER_ID, HOST_NAME } from '../../../../common/es_fields/apm'; import { LABELS, SERVICE_GROUP_SUPPORTED_FIELDS, } from '../../../../common/service_groups'; -const APM_ALERT_SOURCE_FIELDS = [ - ...SERVICE_GROUP_SUPPORTED_FIELDS, - HOST_NAME, - CONTAINER_ID, -]; - export interface SourceDoc { [key: string]: string | string[] | SourceDoc; } -export function getServiceGroupFieldsAgg( +export function getApmAlertSourceFieldsAgg( topHitsOpts: AggregationsTopHitsAggregation = {} ) { return { @@ -30,7 +23,7 @@ export function getServiceGroupFieldsAgg( top_hits: { size: 1, _source: { - includes: APM_ALERT_SOURCE_FIELDS, + includes: SERVICE_GROUP_SUPPORTED_FIELDS, }, ...topHitsOpts, }, @@ -50,6 +43,7 @@ export function getApmAlertSourceFields(bucket?: AggResultBucket) { if (!bucket) { return {}; } + const sourceDoc: SourceDoc = bucket?.source_fields?.hits.hits[0]?._source ?? {}; return flattenSourceDoc(sourceDoc); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/get_service_group_fields.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/get_service_group_fields.test.ts index 3acd1543010e5..92159f416f32b 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/get_service_group_fields.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/get_service_group_fields.test.ts @@ -7,9 +7,9 @@ import { getApmAlertSourceFields, - getServiceGroupFieldsAgg, + getApmAlertSourceFieldsAgg, flattenSourceDoc, -} from './get_service_group_fields'; +} from './get_apm_alert_source_fields'; const mockSourceObj = { service: { @@ -67,7 +67,7 @@ describe('getSourceFields', () => { describe('getSourceFieldsAgg', () => { it('should create a agg for specific source fields', () => { - const agg = getServiceGroupFieldsAgg(); + const agg = getApmAlertSourceFieldsAgg(); expect(agg).toMatchInlineSnapshot(` Object { "source_fields": Object { @@ -79,8 +79,6 @@ describe('getSourceFieldsAgg', () => { "service.environment", "service.language.name", "labels", - "host.name", - "container.id", ], }, "size": 1, @@ -91,7 +89,7 @@ describe('getSourceFieldsAgg', () => { }); it('should accept options for top_hits options', () => { - const agg = getServiceGroupFieldsAgg({ + const agg = getApmAlertSourceFieldsAgg({ sort: [{ 'transaction.duration.us': { order: 'desc' } }], }); expect(agg).toMatchInlineSnapshot(` @@ -105,8 +103,6 @@ describe('getSourceFieldsAgg', () => { "service.environment", "service.language.name", "labels", - "host.name", - "container.id", ], }, "size": 1, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts index 6ee99dd8d89ed..f656b34c4ac75 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts @@ -74,8 +74,8 @@ import { } from '../../register_apm_rule_types'; import { getApmAlertSourceFields, - getServiceGroupFieldsAgg, -} from '../get_service_group_fields'; + getApmAlertSourceFieldsAgg, +} from '../get_apm_alert_source_fields'; import { averageOrPercentileAgg, getMultiTermsSortOrder, @@ -234,7 +234,7 @@ export function registerTransactionDurationRuleType({ aggregationType: ruleParams.aggregationType, transactionDurationField: field, }), - ...getServiceGroupFieldsAgg(), + ...getApmAlertSourceFieldsAgg(), }, }, }, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts index f80ce41953b7d..a32e4575820e4 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/alerts/rule_types/transaction_error_rate/register_transaction_error_rate_rule_type.ts @@ -59,8 +59,8 @@ import { } from '../../register_apm_rule_types'; import { getApmAlertSourceFields, - getServiceGroupFieldsAgg, -} from '../get_service_group_fields'; + getApmAlertSourceFieldsAgg, +} from '../get_apm_alert_source_fields'; import { getGroupByTerms } from '../utils/get_groupby_terms'; import { getGroupByActionVariables } from '../utils/get_groupby_action_variables'; import { getAllGroupByFields } from '../../../../../common/rules/get_all_groupby_fields'; @@ -210,7 +210,7 @@ export function registerTransactionErrorRateRuleType({ terms: { field: EVENT_OUTCOME, }, - aggs: getServiceGroupFieldsAgg(), + aggs: getApmAlertSourceFieldsAgg(), }, }, }, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.test.ts new file mode 100644 index 0000000000000..e18d7f6184c58 --- /dev/null +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.test.ts @@ -0,0 +1,113 @@ +/* + * 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 { getShouldMatchOrNotExistFilter } from '.'; + +describe('getShouldMatchOrNotExistFilter', () => { + describe('when all fields are provided', () => { + const result = getShouldMatchOrNotExistFilter([ + { + field: 'service.name', + value: 'opbeans-node', + }, + { + field: 'container.id', + value: 'my-first-container', + }, + { + field: 'host.name', + value: 'my-only-host', + }, + ]); + + it('returns 3 filters matching the given values', () => { + expect(result).toHaveLength(3); + + expect( + result.flatMap(({ bool }) => + bool.should[0].bool.filter?.map(({ term }) => term) + ) + ).toEqual([ + { 'service.name': 'opbeans-node' }, + { 'container.id': 'my-first-container' }, + { 'host.name': 'my-only-host' }, + ]); + }); + }); + + describe('when no fields are provided', () => { + const result = getShouldMatchOrNotExistFilter([ + { + field: 'service.name', + value: undefined, + }, + { + field: 'container.id', + value: undefined, + }, + { + field: 'host.name', + value: undefined, + }, + ]); + + it('returns no filters', () => { + expect(result).toEqual([]); + }); + }); + + describe('when only `container.id` is provided', () => { + const result = getShouldMatchOrNotExistFilter([ + { + field: 'service.name', + value: undefined, + }, + { + field: 'container.id', + value: 'my-container', + }, + { + field: 'host.name', + value: undefined, + }, + ]); + + it('returns one filter for container.id', () => { + expect(result).toHaveLength(1); + expect(result[0].bool.should).toMatchInlineSnapshot(` + Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "container.id": "my-container", + }, + }, + ], + }, + }, + Object { + "bool": Object { + "must_not": Object { + "bool": Object { + "filter": Array [ + Object { + "exists": Object { + "field": "container.id", + }, + }, + ], + }, + }, + }, + }, + ] + `); + }); + }); +}); diff --git a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts index 1ce41749ba3a0..6084f71e92804 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/assistant_functions/get_log_categories/index.ts @@ -122,7 +122,7 @@ export async function getLogCategories({ } // field/value pairs should match, or the field should not exist -function getShouldMatchOrNotExistFilter( +export function getShouldMatchOrNotExistFilter( keyValuePairs: Array<{ field: string; value?: string; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/index.ts index 6c6df6f5e90d9..1cedf540a545a 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/query/index.ts @@ -11,6 +11,7 @@ import pLimit from 'p-limit'; import Path from 'path'; import { lastValueFrom, startWith } from 'rxjs'; import { promisify } from 'util'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import { FunctionVisibility, MessageRole } from '@kbn/observability-ai-assistant-plugin/common'; import { VisualizeESQLUserIntention, @@ -95,6 +96,7 @@ export function registerQueryFunction({ path: '_query', body: { query, + version: ESQL_LATEST_VERSION, }, }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/visualize_esql.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/visualize_esql.ts index be1e73a8039ff..d66c356997c5a 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/visualize_esql.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/server/functions/visualize_esql.ts @@ -6,6 +6,7 @@ */ import { esFieldTypeToKibanaFieldType } from '@kbn/field-types'; import type { ESQLSearchReponse } from '@kbn/es-types'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import { VisualizeESQLUserIntention } from '@kbn/observability-ai-assistant-plugin/common/functions/visualize_esql'; import { visualizeESQLFunction } from '../../common/functions/visualize_esql'; import { FunctionRegistrationParameters } from '.'; @@ -28,6 +29,7 @@ export function registerVisualizeESQLFunction({ path: '_query', body: { query: performantQuery, + version: ESQL_LATEST_VERSION, }, })) as ESQLSearchReponse; const columns = 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 8f6628298b494..5436043cbfd81 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 @@ -361,7 +361,8 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper suppressionWindow, enrichAlerts, currentTimeOverride, - isRuleExecutionOnly + isRuleExecutionOnly, + maxAlerts ) => { const ruleDataClientWriter = await ruleDataClient.getWriter({ namespace: options.spaceId, @@ -376,6 +377,8 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper const writeAlerts = ruleDataClient.isWriteEnabled() && options.services.shouldWriteAlerts(); + let alertsWereTruncated = false; + if (writeAlerts && alerts.length > 0) { const suppressionWindowStart = dateMath.parse(suppressionWindow, { forceNow: currentTimeOverride, @@ -392,7 +395,12 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper }); if (filteredDuplicates.length === 0) { - return { createdAlerts: [], errors: {}, suppressedAlerts: [] }; + return { + createdAlerts: [], + errors: {}, + suppressedAlerts: [], + alertsWereTruncated, + }; } const suppressionAlertSearchRequest = { @@ -473,7 +481,12 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper }); if (nonSuppressedAlerts.length === 0) { - return { createdAlerts: [], errors: {}, suppressedAlerts: [] }; + return { + createdAlerts: [], + errors: {}, + suppressedAlerts: [], + alertsWereTruncated, + }; } const { alertCandidates, suppressedAlerts: suppressedInMemoryAlerts } = @@ -535,6 +548,11 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper } } + if (maxAlerts && enrichedAlerts.length > maxAlerts) { + enrichedAlerts.length = maxAlerts; + alertsWereTruncated = true; + } + const augmentedAlerts = augmentAlerts({ alerts: enrichedAlerts, options, @@ -548,7 +566,12 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper }); if (bulkResponse == null) { - return { createdAlerts: [], errors: {}, suppressedAlerts: [] }; + return { + createdAlerts: [], + errors: {}, + suppressedAlerts: [], + alertsWereTruncated: false, + }; } const createdAlerts = augmentedAlerts @@ -594,10 +617,16 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper createdAlerts, suppressedAlerts: [...duplicateAlerts, ...suppressedInMemoryAlerts], errors: errorAggregator(bulkResponse.body, [409]), + alertsWereTruncated, }; } else { logger.debug('Writing is disabled.'); - return { createdAlerts: [], errors: {}, suppressedAlerts: [] }; + return { + createdAlerts: [], + errors: {}, + suppressedAlerts: [], + alertsWereTruncated: false, + }; } }, }, diff --git a/x-pack/plugins/rule_registry/server/utils/persistence_types.ts b/x-pack/plugins/rule_registry/server/utils/persistence_types.ts index 1506ad1dd1109..328e5185a2b80 100644 --- a/x-pack/plugins/rule_registry/server/utils/persistence_types.ts +++ b/x-pack/plugins/rule_registry/server/utils/persistence_types.ts @@ -52,11 +52,11 @@ export type SuppressedAlertService = ( params: { spaceId: string } ) => Promise>, currentTimeOverride?: Date, - isRuleExecutionOnly?: boolean + isRuleExecutionOnly?: boolean, + maxAlerts?: number ) => Promise>; -export interface SuppressedAlertServiceResult - extends Omit, 'alertsWereTruncated'> { +export interface SuppressedAlertServiceResult extends PersistenceAlertServiceResult { suppressedAlerts: Array<{ _id: string; _source: T }>; } diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx index 36307494a7711..dae1ffd5709f4 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx @@ -56,7 +56,8 @@ describe('apiKeysManagementApp', () => { element, setBreadcrumbs, history, - theme$: themeServiceMock.createTheme$(), + theme: coreStartMock.theme, + theme$: themeServiceMock.createTheme$(), // needed as a deprecated field in ManagementAppMountParams }); }); diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx index 8e11a1929ca16..2f0ad8f240fa3 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx @@ -40,7 +40,7 @@ async function mountApp( const setBreadcrumbs = jest.fn(); const startServices = await coreMock.createSetup().getStartServices(); - const [{ application }] = startServices; + const [{ application, theme }] = startServices; application.capabilities = { ...application.capabilities, role_mappings: { @@ -57,7 +57,8 @@ async function mountApp( element: container, setBreadcrumbs, history: scopedHistoryMock.create({ pathname }), - theme$: themeServiceMock.createTheme$(), + theme, + theme$: themeServiceMock.createTheme$(), // needed as a deprecated field in ManagementAppMountParams }); }); diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx index 7545c69b7a95f..d9b1478905495 100644 --- a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx @@ -55,7 +55,8 @@ async function mountApp(basePath: string, pathname: string) { element: container, setBreadcrumbs, history: scopedHistoryMock.create({ pathname }), - theme$: themeServiceMock.createTheme$(), + theme: coreStart.theme, + theme$: themeServiceMock.createTheme$(), // needed as a deprecated field in ManagementAppMountParams }); }); diff --git a/x-pack/plugins/security/public/management/users/users_management_app.test.tsx b/x-pack/plugins/security/public/management/users/users_management_app.test.tsx index 992ec3204de21..c442daf8ed273 100644 --- a/x-pack/plugins/security/public/management/users/users_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/users/users_management_app.test.tsx @@ -38,7 +38,8 @@ describe('usersManagementApp', () => { element, setBreadcrumbs, history, - theme$: themeServiceMock.createTheme$(), + theme: coreStartMock.theme, + theme$: themeServiceMock.createTheme$(), // needed as a deprecated field in ManagementAppMountParams }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts index 7393d3ad52fa4..ad06065f856bd 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts @@ -10,6 +10,7 @@ import { getListArrayMock } from '../../../../detection_engine/schemas/types/lis import { getCreateEsqlRulesSchemaMock, getCreateMachineLearningRulesSchemaMock, + getCreateNewTermsRulesSchemaMock, getCreateRulesSchemaMock, getCreateRulesSchemaMockWithDataView, getCreateSavedQueryRulesSchemaMock, @@ -1267,6 +1268,7 @@ describe('rules schema', () => { { ruleType: 'threat_match', ruleMock: getCreateThreatMatchRulesSchemaMock() }, { ruleType: 'query', ruleMock: getCreateRulesSchemaMock() }, { ruleType: 'saved_query', ruleMock: getCreateSavedQueryRulesSchemaMock() }, + { ruleType: 'new_terms', ruleMock: getCreateNewTermsRulesSchemaMock() }, ]; cases.forEach(({ ruleType, ruleMock }) => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts index d7a8b83ec28f4..68c6a44ea45b1 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.gen.ts @@ -504,6 +504,7 @@ export const NewTermsRuleOptionalFields = z.object({ index: IndexPatternArray.optional(), data_view_id: DataViewId.optional(), filters: RuleFilterArray.optional(), + alert_suppression: AlertSuppression.optional(), }); export type NewTermsRuleDefaultableFields = z.infer; diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml index d3a09d8355727..d23662ee9791e 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.schema.yaml @@ -737,6 +737,8 @@ components: $ref: './common_attributes.schema.yaml#/components/schemas/DataViewId' filters: $ref: './common_attributes.schema.yaml#/components/schemas/RuleFilterArray' + alert_suppression: + $ref: './common_attributes.schema.yaml#/components/schemas/AlertSuppression' NewTermsRuleDefaultableFields: type: object diff --git a/x-pack/plugins/security_solution/common/detection_engine/constants.ts b/x-pack/plugins/security_solution/common/detection_engine/constants.ts index 08af29c8f69c7..7e3d7bac5c0f5 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/constants.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/constants.ts @@ -43,5 +43,6 @@ export const SUPPRESSIBLE_ALERT_RULES: Type[] = [ 'threshold', 'saved_query', 'query', + 'new_terms', 'threat_match', ]; diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts index 1db75db42a7d8..a61858a588838 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts @@ -231,11 +231,11 @@ describe('Alert Suppression Rules', () => { expect(isSuppressibleAlertRule('saved_query')).toBe(true); expect(isSuppressibleAlertRule('query')).toBe(true); expect(isSuppressibleAlertRule('threat_match')).toBe(true); + expect(isSuppressibleAlertRule('new_terms')).toBe(true); // Rule types that don't support alert suppression: expect(isSuppressibleAlertRule('eql')).toBe(false); expect(isSuppressibleAlertRule('machine_learning')).toBe(false); - expect(isSuppressibleAlertRule('new_terms')).toBe(false); expect(isSuppressibleAlertRule('esql')).toBe(false); }); @@ -253,11 +253,11 @@ describe('Alert Suppression Rules', () => { expect(isSuppressionRuleConfiguredWithDuration('saved_query')).toBe(true); expect(isSuppressionRuleConfiguredWithDuration('query')).toBe(true); expect(isSuppressionRuleConfiguredWithDuration('threat_match')).toBe(true); + expect(isSuppressionRuleConfiguredWithDuration('new_terms')).toBe(true); // Rule types that don't support alert suppression: expect(isSuppressionRuleConfiguredWithDuration('eql')).toBe(false); expect(isSuppressionRuleConfiguredWithDuration('machine_learning')).toBe(false); - expect(isSuppressionRuleConfiguredWithDuration('new_terms')).toBe(false); expect(isSuppressionRuleConfiguredWithDuration('esql')).toBe(false); }); @@ -274,11 +274,11 @@ describe('Alert Suppression Rules', () => { expect(isSuppressionRuleConfiguredWithGroupBy('saved_query')).toBe(true); expect(isSuppressionRuleConfiguredWithGroupBy('query')).toBe(true); expect(isSuppressionRuleConfiguredWithGroupBy('threat_match')).toBe(true); + expect(isSuppressionRuleConfiguredWithGroupBy('new_terms')).toBe(true); // Rule types that don't support alert suppression: expect(isSuppressionRuleConfiguredWithGroupBy('eql')).toBe(false); expect(isSuppressionRuleConfiguredWithGroupBy('machine_learning')).toBe(false); - expect(isSuppressionRuleConfiguredWithGroupBy('new_terms')).toBe(false); expect(isSuppressionRuleConfiguredWithGroupBy('esql')).toBe(false); }); @@ -300,11 +300,11 @@ describe('Alert Suppression Rules', () => { expect(isSuppressionRuleConfiguredWithMissingFields('saved_query')).toBe(true); expect(isSuppressionRuleConfiguredWithMissingFields('query')).toBe(true); expect(isSuppressionRuleConfiguredWithMissingFields('threat_match')).toBe(true); + expect(isSuppressionRuleConfiguredWithMissingFields('new_terms')).toBe(true); // Rule types that don't support alert suppression: expect(isSuppressionRuleConfiguredWithMissingFields('eql')).toBe(false); expect(isSuppressionRuleConfiguredWithMissingFields('machine_learning')).toBe(false); - expect(isSuppressionRuleConfiguredWithMissingFields('new_terms')).toBe(false); expect(isSuppressionRuleConfiguredWithMissingFields('esql')).toBe(false); }); diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts index f3f9bde438d68..83976ff215e82 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts @@ -38,39 +38,47 @@ import { getFileDownloadId } from '../service/response_actions/get_file_download export class EndpointActionGenerator extends BaseDataGenerator { /** Generate a random endpoint Action request (isolate or unisolate) */ - generate(overrides: DeepPartial = {}): LogsEndpointAction { + generate< + TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes, + TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput, + TMeta extends {} = {} + >( + overrides: DeepPartial> = {} + ): LogsEndpointAction { const timeStamp = overrides['@timestamp'] ? new Date(overrides['@timestamp']) : new Date(); - - return merge( - { - '@timestamp': timeStamp.toISOString(), - agent: { - id: [this.seededUUIDv4()], - }, - EndpointActions: { - action_id: this.seededUUIDv4(), - expiration: this.randomFutureDate(timeStamp), - type: 'INPUT_ACTION', - input_type: 'endpoint', - data: { - command: this.randomResponseActionCommand(), - comment: this.randomString(15), - parameters: undefined, - }, - }, - error: undefined, - user: { - id: this.randomUser(), + const doc: LogsEndpointAction = { + '@timestamp': timeStamp.toISOString(), + agent: { + id: [this.seededUUIDv4()], + }, + EndpointActions: { + action_id: this.seededUUIDv4(), + expiration: this.randomFutureDate(timeStamp), + type: 'INPUT_ACTION', + input_type: 'endpoint', + data: { + command: this.randomResponseActionCommand(), + comment: this.randomString(15), + parameters: undefined, }, - rule: undefined, }, - overrides - ); + error: undefined, + user: { + id: this.randomUser(), + }, + rule: undefined, + }; + + return merge(doc, overrides); } - generateActionEsHit( - overrides: DeepPartial = {} - ): estypes.SearchHit { + generateActionEsHit< + TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes, + TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput, + TMeta extends {} = {} + >( + overrides: DeepPartial> = {} + ): estypes.SearchHit> { return Object.assign(this.toEsSearchHit(this.generate(overrides)), { _index: `.ds-${ENDPOINT_ACTIONS_DS}-some_namespace`, }); diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/sentinelone_data_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/sentinelone_data_generator.ts new file mode 100644 index 0000000000000..54862b4a51549 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/sentinelone_data_generator.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DeepPartial } from 'utility-types'; +import { merge } from 'lodash'; +import type { SearchResponse, SearchHit } from '@elastic/elasticsearch/lib/api/types'; +import { EndpointActionGenerator } from './endpoint_action_generator'; +import { SENTINEL_ONE_ACTIVITY_INDEX } from '../..'; +import type { + LogsEndpointAction, + SentinelOneActivityEsDoc, + EndpointActionDataParameterTypes, + EndpointActionResponseDataOutput, +} from '../types'; + +export class SentinelOneDataGenerator extends EndpointActionGenerator { + generate< + TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes, + TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput, + TMeta extends {} = {} + >( + overrides: DeepPartial> = {} + ): LogsEndpointAction { + return super.generate({ + EndpointActions: { + input_type: 'sentinel_one', + }, + ...overrides, + }) as LogsEndpointAction; + } + + /** Generate a SentinelOne activity index ES doc */ + generateActivityEsDoc( + overrides: DeepPartial = {} + ): SentinelOneActivityEsDoc { + const doc: SentinelOneActivityEsDoc = { + sentinel_one: { + activity: { + agent: { + id: this.seededUUIDv4(), + }, + updated_at: '2024-03-29T13:45:21.723Z', + description: { + primary: 'Some description here', + }, + id: this.seededUUIDv4(), + type: 1001, + }, + }, + }; + + return merge(doc, overrides); + } + + generateActivityEsSearchHit( + overrides: DeepPartial = {} + ): SearchHit { + const hit = this.toEsSearchHit( + this.generateActivityEsDoc(overrides), + SENTINEL_ONE_ACTIVITY_INDEX + ); + + hit.inner_hits = { + first_found: { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + hits: { hits: [this.toEsSearchHit(hit._source!, hit._index)] }, + }, + }; + + return hit; + } + + generateActivityEsSearchResponse( + docs: Array> = [this.generateActivityEsSearchHit()] + ): SearchResponse { + return this.toEsSearchResponse(docs); + } +} diff --git a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/sentinel_one.ts b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/sentinel_one.ts new file mode 100644 index 0000000000000..761e1de67cd28 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/sentinel_one.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +/** + * Index name where the SentinelOne activity log is written to by the SentinelOne integration + */ +export const SENTINEL_ONE_ACTIVITY_INDEX = 'logs-sentinel_one.activity-default'; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index 68867e92d7294..9fc98799db962 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -14,6 +14,7 @@ export * from './os'; export * from './trusted_apps'; export * from './utility_types'; export * from './agents'; +export * from './sentinel_one'; export type { ConditionEntriesMap, ConditionEntry } from './exception_list_items'; /** diff --git a/x-pack/plugins/security_solution/common/endpoint/types/sentinel_one.ts b/x-pack/plugins/security_solution/common/endpoint/types/sentinel_one.ts new file mode 100644 index 0000000000000..447fc6d037a2b --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/types/sentinel_one.ts @@ -0,0 +1,55 @@ +/* + * 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. + */ + +/** + * The `activity` document ingested from SentinelOne via the integration + * + * NOTE: not all properties are currently mapped below. Check the index definition if wanting to + * see what else is available and add it bellow if needed + */ +export interface SentinelOneActivityEsDoc { + sentinel_one: { + activity: { + agent: { + /** This is the internal ID of the host in sentinelOne (NOT the agent's UUID) */ + id: string; + }; + updated_at: string; + description: { + primary: string; + secondary?: string; + }; + id: string; + /** The activity type. Valid values can be retrieved from S1 via API: `/web/api/v2.1/activities/types` */ + type: number; + }; + }; +} + +export interface SentinelOneActionRequestCommonMeta { + /** The S1 agent id */ + agentId: string; + /** The S1 agent assigned UUID */ + agentUUID: string; + /** The host name */ + hostName: string; +} + +/** Metadata capture for the isolation actions (`isolate` and `release`) */ +export type SentinelOneIsolationRequestMeta = SentinelOneActionRequestCommonMeta; + +/** Metadata captured when creating the isolation response document in ES for both `isolate` and `release` */ +export interface SentinelOneIsolationResponseMeta { + /** The document ID in the Elasticsearch S1 activity index that was used to complete the response action */ + elasticDocId: string; + /** The SentinelOne activity log entry ID */ + activityLogEntryId: string; + /** The SentinelOne activity log entry type */ + activityLogEntryType: number; + /** The SentinelOne activity log primary description */ + activityLogEntryDescription: string; +} diff --git a/x-pack/plugins/security_solution/common/endpoint/utils/package_v2.ts b/x-pack/plugins/security_solution/common/endpoint/utils/package_v2.ts index 5717f8d601502..56b23ed956770 100644 --- a/x-pack/plugins/security_solution/common/endpoint/utils/package_v2.ts +++ b/x-pack/plugins/security_solution/common/endpoint/utils/package_v2.ts @@ -5,10 +5,10 @@ * 2.0. */ -import semverLte from 'semver/functions/lte'; +// import semverLte from 'semver/functions/lte'; // switch to "v2" logic -const MIN_ENDPOINT_PACKAGE_V2_VERSION = '8.14.0-prerelease.1'; +// const MIN_ENDPOINT_PACKAGE_V2_VERSION = '8.14.0-prerelease.1'; export function isEndpointPackageV2(version: string) { - return semverLte(MIN_ENDPOINT_PACKAGE_V2_VERSION, version); + return false; } diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index afcd9700e4caa..cd98a9ef812e4 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -170,6 +170,11 @@ export const allowedExperimentalValues = Object.freeze({ */ riskEnginePrivilegesRouteEnabled: true, + /** + * Enables alerts suppression for new terms rules + */ + alertSuppressionForNewTermsRuleEnabled: false, + /** * Enables experimental Experimental S1 integration data to be available in Analyzer */ diff --git a/x-pack/plugins/security_solution/common/index.ts b/x-pack/plugins/security_solution/common/index.ts index 24181c66f7444..46dfd562ee8a6 100644 --- a/x-pack/plugins/security_solution/common/index.ts +++ b/x-pack/plugins/security_solution/common/index.ts @@ -22,6 +22,7 @@ export { export { ELASTIC_SECURITY_RULE_ID } from './detection_engine/constants'; export { ENABLED_FIELD } from './detection_engine/rule_management/rule_fields'; export { allowedExperimentalValues, type ExperimentalFeatures } from './experimental_features'; +export { SENTINEL_ONE_ACTIVITY_INDEX } from './endpoint/service/response_actions/sentinel_one'; // Careful of exporting anything from this file as any file(s) you export here will cause your page bundle size to increase. // If you're using functions/types/etc... internally it's best to import directly from their paths than expose the functions/types/etc... here. diff --git a/x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx b/x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx index 20dfc140c4138..10234da2fac2f 100644 --- a/x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/charts/common.test.tsx @@ -23,15 +23,6 @@ import { LEGACY_LIGHT_THEME, LEGACY_DARK_THEME } from '@elastic/charts'; jest.mock('../../lib/kibana'); -jest.mock('@elastic/charts', () => { - const original = jest.requireActual('@elastic/charts'); - - return { - ...original, - getSpecId: jest.fn(() => {}), - }; -}); - describe('WrappedByAutoSizer', () => { it('should render correct default height', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap index 46c33d5102feb..3ef2ecf88449b 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/header_section/__snapshots__/index.test.tsx.snap @@ -39,7 +39,7 @@ exports[`HeaderSection it renders 1`] = ` -

Test title -

+
diff --git a/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx index 70c6e6778a604..75cd089a87e36 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_section/index.tsx @@ -169,7 +169,7 @@ const HeaderSectionComponent: React.FC = ({ )} -

+

{title} {tooltip && ( <> @@ -182,7 +182,7 @@ const HeaderSectionComponent: React.FC = ({ /> )} -

+
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx index f341476c4d8f9..d76c86596d1d2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/description_step/index.test.tsx @@ -575,7 +575,7 @@ describe('description_step', () => { }); describe('alert suppression', () => { - const ruleTypesWithoutSuppression: Type[] = ['eql', 'esql', 'machine_learning', 'new_terms']; + const ruleTypesWithoutSuppression: Type[] = ['eql', 'esql', 'machine_learning']; const suppressionFields = { groupByDuration: { unit: 'm', diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx index 49eba2d124d31..fd22c25308a27 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/schema.tsx @@ -19,9 +19,9 @@ import { import { isEsqlRule, isNewTermsRule, - isQueryRule, isThreatMatchRule, isThresholdRule, + isSuppressionRuleConfiguredWithGroupBy, } from '../../../../../common/detection_engine/utils'; import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../../common/constants'; import { isMlRule } from '../../../../../common/machine_learning/helpers'; @@ -574,28 +574,18 @@ export const schema: FormSchema = { }, ], }, - groupByFields: { + newTermsFields: { type: FIELD_TYPES.COMBO_BOX, label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabel', + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel', { - defaultMessage: 'Suppress alerts by', + defaultMessage: 'Fields', } ), - labelAppend: ( - - {i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabelAppend', - { - defaultMessage: 'Optional (Technical Preview)', - } - )} - - ), helpText: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldGroupByFieldHelpText', + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNewTermsFieldHelpText', { - defaultMessage: 'Select field(s) to use for suppressing extra alerts', + defaultMessage: 'Select a field to check for new terms.', } ), validations: [ @@ -604,17 +594,36 @@ export const schema: FormSchema = { ...args: Parameters ): ReturnType> | undefined => { const [{ formData }] = args; - const needsValidation = - isQueryRule(formData.ruleType) || isThreatMatchRule(formData.ruleType); + const needsValidation = isNewTermsRule(formData.ruleType); + if (!needsValidation) { + return; + } + + return fieldValidators.emptyField( + i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin', + { + defaultMessage: 'A minimum of one field is required.', + } + ) + )(...args); + }, + }, + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ formData }] = args; + const needsValidation = isNewTermsRule(formData.ruleType); if (!needsValidation) { return; } return fieldValidators.maxLengthField({ - length: 3, + length: MAX_NUMBER_OF_NEW_TERMS_FIELDS, message: i18n.translate( - 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.groupByFieldsMax', + 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsFieldsMax', { - defaultMessage: 'Number of grouping fields must be at most 3', + defaultMessage: 'Number of fields must be 3 or less.', } ), })(...args); @@ -622,43 +631,42 @@ export const schema: FormSchema = { }, ], }, - groupByRadioSelection: {}, - groupByDuration: { + historyWindowSize: { label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByDurationValueLabel', + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.historyWindowSizeLabel', { - defaultMessage: 'Suppress alerts for', + defaultMessage: 'History Window Size', } ), helpText: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldGroupByDurationValueHelpText', - { - defaultMessage: 'Suppress alerts for', - } - ), - value: {}, - unit: {}, - }, - suppressionMissingFields: { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.suppressionMissingFieldsLabel', + 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.historyWindowSizeHelpText', { - defaultMessage: 'If a suppression field is missing', + defaultMessage: "New terms rules only alert if terms don't appear in historical data.", } ), }, - newTermsFields: { + groupByFields: { type: FIELD_TYPES.COMBO_BOX, label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsLabel', + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabel', { - defaultMessage: 'Fields', + defaultMessage: 'Suppress alerts by', } ), + labelAppend: ( + + {i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabelAppend', + { + defaultMessage: 'Optional (Technical Preview)', + } + )} + + ), helpText: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldNewTermsFieldHelpText', + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldGroupByFieldHelpText', { - defaultMessage: 'Select a field to check for new terms.', + defaultMessage: 'Select field(s) to use for suppressing extra alerts', } ), validations: [ @@ -667,36 +675,17 @@ export const schema: FormSchema = { ...args: Parameters ): ReturnType> | undefined => { const [{ formData }] = args; - const needsValidation = isNewTermsRule(formData.ruleType); - if (!needsValidation) { - return; - } + const needsValidation = isSuppressionRuleConfiguredWithGroupBy(formData.ruleType); - return fieldValidators.emptyField( - i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.newTermsFieldsMin', - { - defaultMessage: 'A minimum of one field is required.', - } - ) - )(...args); - }, - }, - { - validator: ( - ...args: Parameters - ): ReturnType> | undefined => { - const [{ formData }] = args; - const needsValidation = isNewTermsRule(formData.ruleType); if (!needsValidation) { return; } return fieldValidators.maxLengthField({ - length: MAX_NUMBER_OF_NEW_TERMS_FIELDS, + length: 3, message: i18n.translate( - 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.newTermsFieldsMax', + 'xpack.securitySolution.detectionEngine.validations.stepDefineRule.groupByFieldsMax', { - defaultMessage: 'Number of fields must be 3 or less.', + defaultMessage: 'Number of grouping fields must be at most 3', } ), })(...args); @@ -704,17 +693,28 @@ export const schema: FormSchema = { }, ], }, - historyWindowSize: { + groupByRadioSelection: {}, + groupByDuration: { label: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.historyWindowSizeLabel', + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByDurationValueLabel', { - defaultMessage: 'History Window Size', + defaultMessage: 'Suppress alerts for', } ), helpText: i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.historyWindowSizeHelpText', + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldGroupByDurationValueHelpText', { - defaultMessage: "New terms rules only alert if terms don't appear in historical data.", + defaultMessage: 'Suppress alerts for', + } + ), + value: {}, + unit: {}, + }, + suppressionMissingFields: { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.suppressionMissingFieldsLabel', + { + defaultMessage: 'If a suppression field is missing', } ), }, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_experimental_feature_fields_transform.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_experimental_feature_fields_transform.ts index c035fef5af6e4..67e55892fd8eb 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_experimental_feature_fields_transform.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_define_rule/use_experimental_feature_fields_transform.ts @@ -7,6 +7,8 @@ import { useCallback } from 'react'; import type { DefineStepRule } from '../../../../detections/pages/detection_engine/rules/types'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { isNewTermsRule } from '../../../../../common/detection_engine/utils'; /** * transforms DefineStepRule fields according to experimental feature flags @@ -14,9 +16,31 @@ import type { DefineStepRule } from '../../../../detections/pages/detection_engi export const useExperimentalFeatureFieldsTransform = >(): (( fields: T ) => T) => { - const transformer = useCallback((fields: T) => { - return fields; - }, []); + const isAlertSuppressionForNewTermsRuleEnabled = useIsExperimentalFeatureEnabled( + 'alertSuppressionForNewTermsRuleEnabled' + ); + + const transformer = useCallback( + (fields: T) => { + const isNewTermsSuppressionDisabled = isNewTermsRule(fields.ruleType) + ? !isAlertSuppressionForNewTermsRuleEnabled + : false; + + // reset any alert suppression values hidden behind feature flag + if (isNewTermsSuppressionDisabled) { + return { + ...fields, + groupByFields: [], + groupByRadioSelection: undefined, + groupByDuration: undefined, + suppressionMissingFields: undefined, + }; + } + + return fields; + }, + [isAlertSuppressionForNewTermsRuleEnabled] + ); return transformer; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts index 924dc4a62fd70..fb45ca1efe273 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts @@ -490,6 +490,7 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep query: ruleFields.queryBar?.query?.query as string, new_terms_fields: ruleFields.newTermsFields, history_window_start: `now-${ruleFields.historyWindowSize}`, + ...alertSuppressionFields, } : isEsqlFields(ruleFields) && !('index' in ruleFields) ? { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx index 780ceaabf9651..4a9f97933b49b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_details/rule_definition_section.tsx @@ -645,6 +645,28 @@ const prepareDefinitionSectionListItems = ( }); } + if ('new_terms_fields' in rule && rule.new_terms_fields && rule.new_terms_fields.length > 0) { + definitionSectionListItems.push({ + title: ( + + {i18n.NEW_TERMS_FIELDS_FIELD_LABEL} + + ), + description: , + }); + } + + if ('history_window_start' in rule) { + definitionSectionListItems.push({ + title: ( + + {i18n.HISTORY_WINDOW_SIZE_FIELD_LABEL} + + ), + description: , + }); + } + if (isSuppressionEnabled && 'alert_suppression' in rule && rule.alert_suppression) { if ('group_by' in rule.alert_suppression) { definitionSectionListItems.push({ @@ -682,28 +704,6 @@ const prepareDefinitionSectionListItems = ( } } - if ('new_terms_fields' in rule && rule.new_terms_fields && rule.new_terms_fields.length > 0) { - definitionSectionListItems.push({ - title: ( - - {i18n.NEW_TERMS_FIELDS_FIELD_LABEL} - - ), - description: , - }); - } - - if ('history_window_start' in rule) { - definitionSectionListItems.push({ - title: ( - - {i18n.HISTORY_WINDOW_SIZE_FIELD_LABEL} - - ), - description: , - }); - } - return definitionSectionListItems; }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.test.tsx index cab113ecd1393..d5c9f1f07d007 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.test.tsx @@ -7,8 +7,28 @@ import { renderHook } from '@testing-library/react-hooks'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { useAlertSuppression } from './use_alert_suppression'; +import * as useIsExperimentalFeatureEnabledMock from '../../../common/hooks/use_experimental_features'; + +jest + .spyOn(useIsExperimentalFeatureEnabledMock, 'useIsExperimentalFeatureEnabled') + .mockReturnValue(false); describe('useAlertSuppression', () => { + it('should return isSuppressionEnabled false if rule Type exists in SUPPRESSIBLE_ALERT_RULES and Feature Flag is disabled', () => { + const { result } = renderHook(() => useAlertSuppression('new_terms')); + + expect(result.current.isSuppressionEnabled).toBe(false); + }); + + it('should return isSuppressionEnabled true if rule Type exists in SUPPRESSIBLE_ALERT_RULES and Feature Flag is enabled', () => { + jest + .spyOn(useIsExperimentalFeatureEnabledMock, 'useIsExperimentalFeatureEnabled') + .mockImplementationOnce(() => true); + const { result } = renderHook(() => useAlertSuppression('new_terms')); + + expect(result.current.isSuppressionEnabled).toBe(true); + }); + it('should return the correct isSuppressionEnabled value fot threat_match rule type', () => { const { result } = renderHook(() => useAlertSuppression('threat_match')); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.tsx index ba1bb94a49ae3..f4d1b3d22e82b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/use_alert_suppression.tsx @@ -7,17 +7,29 @@ import { useCallback } from 'react'; import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { isSuppressibleAlertRule } from '../../../../common/detection_engine/utils'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; export interface UseAlertSuppressionReturn { isSuppressionEnabled: boolean; } export const useAlertSuppression = (ruleType: Type | undefined): UseAlertSuppressionReturn => { + const isAlertSuppressionForNewTermsRuleEnabled = useIsExperimentalFeatureEnabled( + 'alertSuppressionForNewTermsRuleEnabled' + ); + const isSuppressionEnabledForRuleType = useCallback(() => { - if (!ruleType) return false; + if (!ruleType) { + return false; + } + + // Remove this condition when the Feature Flag for enabling Suppression in the New terms rule is removed. + if (ruleType === 'new_terms') { + return isSuppressibleAlertRule(ruleType) && isAlertSuppressionForNewTermsRuleEnabled; + } return isSuppressibleAlertRule(ruleType); - }, [ruleType]); + }, [ruleType, isAlertSuppressionForNewTermsRuleEnabled]); return { isSuppressionEnabled: isSuppressionEnabledForRuleType(), diff --git a/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap b/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap index b0b2754c24d3b..bf74c41fa303f 100644 --- a/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_host_table.test.tsx.snap @@ -104,7 +104,7 @@ exports[`Authentication Host Table Component rendering it renders the host authe
-

@@ -113,7 +113,7 @@ exports[`Authentication Host Table Component rendering it renders the host authe > Authentications -

+
diff --git a/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_user_table.test.tsx.snap b/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_user_table.test.tsx.snap index 3d31cd3759ffe..4acce15364b6b 100644 --- a/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_user_table.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/explore/components/authentication/__snapshots__/authentications_user_table.test.tsx.snap @@ -104,7 +104,7 @@ exports[`Authentication User Table Component rendering it renders the user authe
-

@@ -113,7 +113,7 @@ exports[`Authentication User Table Component rendering it renders the user authe > Authentications -

+
diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts index b6040691c485f..7fd516d217b95 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifact_tabs_in_policy_details.cy.ts @@ -59,7 +59,8 @@ const visitArtifactTab = (tabId: string) => { cy.get(`#${tabId}`).click(); }; -describe('Artifact tabs in Policy Details page', { tags: ['@ess', '@serverless'] }, () => { +// Failing: See https://github.com/elastic/kibana/issues/171644 +describe.skip('Artifact tabs in Policy Details page', { tags: ['@ess', '@serverless'] }, () => { let endpointData: ReturnTypeFromChainable | undefined; before(() => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts index 8d21ff3804364..d1b724acad6ef 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts/artifacts_mocked_data.cy.ts @@ -32,7 +32,8 @@ const loginWithoutAccess = (url: string) => { }; // Flaky: https://github.com/elastic/kibana/issues/171168 -describe('Artifacts pages', { tags: ['@ess', '@serverless'] }, () => { +// Failing: See https://github.com/elastic/kibana/issues/171168 +describe.skip('Artifacts pages', { tags: ['@ess', '@serverless'] }, () => { let endpointData: ReturnTypeFromChainable | undefined; before(() => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts index 344c4b0b3bd21..c818599d06410 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/automated_response_actions/results.cy.ts @@ -14,7 +14,8 @@ import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts' import { login, ROLE } from '../../tasks/login'; -describe('Results', { tags: ['@ess', '@serverless'] }, () => { +// FLAKY: https://github.com/elastic/kibana/issues/171665 +describe.skip('Results', { tags: ['@ess', '@serverless'] }, () => { let endpointData: ReturnTypeFromChainable | undefined; let alertData: ReturnTypeFromChainable | undefined; const [endpointAgentId, endpointHostname] = generateRandomStringName(2); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts index e737779ad8ff9..4968fb9bda21a 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/responder.cy.ts @@ -21,7 +21,8 @@ import { indexNewCase } from '../../tasks/index_new_case'; import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; import { indexEndpointRuleAlerts } from '../../tasks/index_endpoint_rule_alerts'; -describe('When accessing Endpoint Response Console', { tags: ['@ess', '@serverless'] }, () => { +// Failing: See https://github.com/elastic/kibana/issues/169894 +describe.skip('When accessing Endpoint Response Console', { tags: ['@ess', '@serverless'] }, () => { const performResponderSanityChecks = () => { openResponderActionLogFlyout(); // Ensure the popover in the action log date quick select picker is accessible diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_actions_history.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_actions_history.cy.ts index a36f7e3beaec4..fb953d22f23be 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_actions_history.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/response_actions/response_actions_history.cy.ts @@ -10,7 +10,8 @@ import { indexEndpointHosts } from '../../tasks/index_endpoint_hosts'; import { login } from '../../tasks/login'; import { loadPage } from '../../tasks/common'; -describe('Response actions history page', { tags: ['@ess', '@serverless'] }, () => { +// Failing: See https://github.com/elastic/kibana/issues/172549 +describe.skip('Response actions history page', { tags: ['@ess', '@serverless'] }, () => { let endpointData: ReturnTypeFromChainable; before(() => { diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/endpoint_list_with_security_essentials.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/endpoint_list_with_security_essentials.cy.ts index 9cd298535df26..7f003848db88c 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/endpoint_list_with_security_essentials.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/endpoint_list_with_security_essentials.cy.ts @@ -15,7 +15,8 @@ import { visitEndpointList, } from '../../screens'; -describe( +// Failing: See https://github.com/elastic/kibana/issues/171643 +describe.skip( 'When on the Endpoint List in Security Essentials PLI', { tags: ['@serverless'], diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/essentials_with_endpoint.roles.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/essentials_with_endpoint.roles.cy.ts index 4e0ae2080a97b..cdef86bb1d4a7 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/essentials_with_endpoint.roles.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/serverless/roles/essentials_with_endpoint.roles.cy.ts @@ -23,7 +23,8 @@ import { ensurePolicyDetailsPageAuthzAccess, } from '../../../screens'; -describe( +// Failing: See https://github.com/elastic/kibana/issues/170985 +describe.skip( 'Roles for Security Essential PLI with Endpoint Essentials addon', { tags: ['@serverless'], diff --git a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx index e91a7487f6491..c10fab09fa47b 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/overview.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/overview.tsx @@ -5,9 +5,16 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiShowFor } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiShowFor, + EuiScreenReaderOnly, +} from '@elastic/eui'; import React, { useCallback, useState, useMemo } from 'react'; +import { OVERVIEW } from '../../app/translations'; import { InputsModelId } from '../../common/store/inputs/constants'; import { FiltersGlobal } from '../../common/components/filters_global'; import { SiemSearchBar } from '../../common/components/search_bar'; @@ -69,6 +76,10 @@ const OverviewComponent = () => { return ( <> + +

{OVERVIEW}

+
+ {indicesExist ? ( <> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/super_select.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/super_select.tsx deleted file mode 100644 index e26d8339a2134..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/super_select.tsx +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - Duplicated EuiSuperSelect, because due to the recent changes there is no way to pass panelClassName - prop to EuiInputPopover, which doesn't allow us to properly style the EuiInputPopover panel - (we want the panel to be wider than the input) -*/ - -import type { EuiSuperSelectProps } from '@elastic/eui'; -import { - EuiScreenReaderOnly, - EuiSuperSelectControl, - EuiInputPopover, - EuiContextMenuItem, - keys, - EuiI18n, -} from '@elastic/eui'; -import React, { Component } from 'react'; -import classNames from 'classnames'; - -enum ShiftDirection { - BACK = 'back', - FORWARD = 'forward', -} - -export class EuiSuperSelect extends Component> { - static defaultProps = { - compressed: false, - fullWidth: false, - hasDividers: false, - isInvalid: false, - isLoading: false, - }; - - private itemNodes: Array = []; - private _isMounted: boolean = false; - - state = { - isPopoverOpen: this.props.isOpen || false, - }; - - componentDidMount() { - this._isMounted = true; - if (this.props.isOpen) { - this.openPopover(); - } - } - - componentWillUnmount() { - this._isMounted = false; - } - - setItemNode = (node: HTMLButtonElement | null, index: number) => { - this.itemNodes[index] = node; - }; - - openPopover = () => { - this.setState({ - isPopoverOpen: true, - }); - - const focusSelected = () => { - const indexOfSelected = this.props.options.reduce((acc, option, index) => { - if (acc != null) return acc; - if (option == null) return null; - return option.value === this.props.valueOfSelected ? index : null; - }, null); - - requestAnimationFrame(() => { - if (!this._isMounted) { - return; - } - - if (this.props.valueOfSelected != null) { - if (indexOfSelected != null) { - this.focusItemAt(indexOfSelected); - } else { - focusSelected(); - } - } - }); - }; - - requestAnimationFrame(focusSelected); - }; - - closePopover = () => { - this.setState({ - isPopoverOpen: false, - }); - }; - - itemClicked = (value: T) => { - this.setState({ - isPopoverOpen: false, - }); - if (this.props.onChange) { - this.props.onChange(value); - } - }; - - onSelectKeyDown = (event: React.KeyboardEvent) => { - if (event.key === keys.ARROW_UP || event.key === keys.ARROW_DOWN) { - event.preventDefault(); - event.stopPropagation(); - this.openPopover(); - } - }; - - onItemKeyDown = (event: React.KeyboardEvent) => { - switch (event.key) { - case keys.ESCAPE: - // close the popover and prevent ancestors from handling - event.preventDefault(); - event.stopPropagation(); - this.closePopover(); - break; - - case keys.TAB: - // no-op - event.preventDefault(); - event.stopPropagation(); - break; - - case keys.ARROW_UP: - event.preventDefault(); - event.stopPropagation(); - this.shiftFocus(ShiftDirection.BACK); - break; - - case keys.ARROW_DOWN: - event.preventDefault(); - event.stopPropagation(); - this.shiftFocus(ShiftDirection.FORWARD); - break; - } - }; - - focusItemAt(index: number) { - const targetElement = this.itemNodes[index]; - if (targetElement != null) { - targetElement.focus(); - } - } - - shiftFocus(direction: ShiftDirection) { - const currentIndex = this.itemNodes.indexOf(document.activeElement as HTMLButtonElement); - let targetElementIndex: number; - - if (currentIndex === -1) { - // somehow the select options has lost focus - targetElementIndex = 0; - } else { - if (direction === ShiftDirection.BACK) { - targetElementIndex = currentIndex === 0 ? this.itemNodes.length - 1 : currentIndex - 1; - } else { - targetElementIndex = currentIndex === this.itemNodes.length - 1 ? 0 : currentIndex + 1; - } - } - - this.focusItemAt(targetElementIndex); - } - - render() { - const { - className, - options, - valueOfSelected, - onChange, - isOpen, - isInvalid, - hasDividers, - itemClassName, - itemLayoutAlign, - fullWidth, - popoverProps, - compressed, - ...rest - } = this.props; - - const popoverClasses = classNames('euiSuperSelect', popoverProps?.className); - - const buttonClasses = classNames( - { - 'euiSuperSelect--isOpen__button': this.state.isPopoverOpen, - }, - className - ); - - const itemClasses = classNames( - 'euiSuperSelect__item', - { - 'euiSuperSelect__item--hasDividers': hasDividers, - }, - itemClassName - ); - - const button = ( - - ); - - const items = options.map((option, index) => { - const { value, dropdownDisplay, inputDisplay, ...optionRest } = option; - - return ( - this.itemClicked(value)} - onKeyDown={this.onItemKeyDown} - layoutAlign={itemLayoutAlign} - buttonRef={(node) => this.setItemNode(node, index)} - role="option" - id={value} - aria-selected={valueOfSelected === value} - {...optionRest} - > - {dropdownDisplay || inputDisplay} - - ); - }); - - return ( - - -

- -

-
-
- {items} -
-
- ); - } -} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx index 7289f32fca36e..c1927579f8019 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_super_select/index.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { EuiSelectableOption } from '@elastic/eui'; -import { EuiInputPopover, EuiFieldText, htmlIdGenerator } from '@elastic/eui'; +import type { EuiSelectableOption, EuiFieldTextProps } from '@elastic/eui'; +import { EuiInputPopover, EuiFieldText, htmlIdGenerator, keys } from '@elastic/eui'; import React, { memo, useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; @@ -90,15 +90,20 @@ const SearchTimelineSuperSelectComponent: React.FC { + if (event.key === keys.ENTER) { + setIsPopoverOpen(true); + } + }, []); + const popoverId = useMemo(() => htmlIdGenerator('searchTimelinePopover')(), []); const superSelect = useMemo( () => ( ), - [ariaLabel, handleOpenPopover, isDisabled, isPopoverOpen, popoverId, timelineTitle] + [ + ariaLabel, + handleKeyboardOpen, + handleOpenPopover, + isDisabled, + isPopoverOpen, + popoverId, + timelineTitle, + ] ); const handleGetSelectableOptions = useCallback( diff --git a/x-pack/plugins/security_solution/scripts/endpoint/sentinelone_host/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/sentinelone_host/index.ts index 2811fe9e57d09..1d59f4d9cfef0 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/sentinelone_host/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/sentinelone_host/index.ts @@ -121,7 +121,7 @@ const runCli: RunFn = async ({ log, flags }) => { ).data; // Avoid enrolling another VM with SentinelOne if we already have one running - const hostVm = + const s1HostVm = forceNewS1Host || runningS1VMs.length === 0 ? await createVm({ type: 'multipass', @@ -204,9 +204,12 @@ const runCli: RunFn = async ({ log, flags }) => { createDetectionEngineSentinelOneRuleIfNeeded(kbnClient, log), ]); + // Trigger an alert on the SentinelOn host so that we get an alert back in Kibana + await s1HostVm.exec('nslookup elastic.co'); + log.info(`Done! -${hostVm.info()} +${s1HostVm.info()} ${agentPolicyVm ? `${agentPolicyVm.info()}\n` : ''} ${await getMultipassVmCountNotice(2)} `); diff --git a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts index d4fa3866402e6..d9418b81b538d 100644 --- a/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts +++ b/x-pack/plugins/security_solution/scripts/run_cypress/parallel_serverless.ts @@ -100,7 +100,8 @@ const getApiKeyFromElasticCloudJsonFile = (): string | undefined => { async function createSecurityProject( projectName: string, apiKey: string, - productTypes: ProductType[] + productTypes: ProductType[], + commit: string ): Promise { const body: CreateProjectRequestBody = { name: projectName, @@ -110,10 +111,12 @@ async function createSecurityProject( log.info(`Kibana override flag equals to ${process.env.KIBANA_MKI_USE_LATEST_COMMIT}!`); if ( - process.env.KIBANA_MKI_USE_LATEST_COMMIT && - process.env.KIBANA_MKI_USE_LATEST_COMMIT === '1' + (process.env.KIBANA_MKI_USE_LATEST_COMMIT && + process.env.KIBANA_MKI_USE_LATEST_COMMIT === '1') || + commit ) { - const kibanaOverrideImage = `${process.env.BUILDKITE_COMMIT?.substring(0, 12)}`; + const override = commit ? commit : process.env.BUILDKITE_COMMIT; + const kibanaOverrideImage = `${override?.substring(0, 12)}`; log.info( `Overriding Kibana image in the MKI with docker.elastic.co/kibana-ci/kibana-serverless:sec-sol-qg-${kibanaOverrideImage}` ); @@ -446,6 +449,11 @@ export const cli = () => { alias: 'ca', type: 'boolean', default: true, + }) + .option('commit', { + alias: 'c', + type: 'string', + default: '', }); log.info(` @@ -472,6 +480,7 @@ ${JSON.stringify(argv, null, 2)} const tier: string = argv.tier; const endpointAddon: boolean = argv.endpointAddon; const cloudAddon: boolean = argv.cloudAddon; + const commit: string = argv.commit; log.info(` ---------------------------------------------- @@ -557,7 +566,12 @@ ${JSON.stringify(cypressConfigFile, null, 2)} log.info(`${id}: Creating project ${PROJECT_NAME}...`); // Creating project for the test to run - const project = await createSecurityProject(PROJECT_NAME, API_KEY, productTypes); + const project = await createSecurityProject( + PROJECT_NAME, + API_KEY, + productTypes, + commit + ); if (!project) { log.info('Failed to create project.'); diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks/index.ts b/x-pack/plugins/security_solution/server/endpoint/mocks/index.ts new file mode 100644 index 0000000000000..599c6328855e5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/mocks/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './mocks'; diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts similarity index 90% rename from x-pack/plugins/security_solution/server/endpoint/mocks.ts rename to x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts index f3d39737a7cf2..1fbfeef9382e5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks/mocks.ts @@ -48,32 +48,31 @@ import { createCasesClientMock } from '@kbn/cases-plugin/server/client/mocks'; import type { AddVersionOpts, VersionedRouteConfig } from '@kbn/core-http-server'; import { unsecuredActionsClientMock } from '@kbn/actions-plugin/server/unsecured_actions_client/unsecured_actions_client.mock'; import type { PluginStartContract } from '@kbn/actions-plugin/server'; -import { responseActionsClientMock } from './services/actions/clients/mocks'; -import { getEndpointAuthzInitialStateMock } from '../../common/endpoint/service/authz/mocks'; -import { createMockConfig, requestContextMock } from '../lib/detection_engine/routes/__mocks__'; +import { responseActionsClientMock } from '../services/actions/clients/mocks'; +import { getEndpointAuthzInitialStateMock } from '../../../common/endpoint/service/authz/mocks'; +import { createMockConfig, requestContextMock } from '../../lib/detection_engine/routes/__mocks__'; import type { EndpointAppContextService, EndpointAppContextServiceSetupContract, EndpointAppContextServiceStartContract, -} from './endpoint_app_context_services'; -import type { ManifestManager } from './services/artifacts/manifest_manager/manifest_manager'; -import { getManifestManagerMock } from './services/artifacts/manifest_manager/manifest_manager.mock'; -import type { EndpointAppContext } from './types'; +} from '../endpoint_app_context_services'; +import type { ManifestManager } from '../services/artifacts/manifest_manager/manifest_manager'; +import { getManifestManagerMock } from '../services/artifacts/manifest_manager/manifest_manager.mock'; +import type { EndpointAppContext } from '../types'; import { allowedExperimentalValues, parseExperimentalConfigValue, -} from '../../common/experimental_features'; -import { requestContextFactoryMock } from '../request_context_factory.mock'; -import { EndpointMetadataService } from './services/metadata'; -import type { SecuritySolutionRequestHandlerContextMock } from '../lib/detection_engine/routes/__mocks__/request_context'; -import { createMockClients } from '../lib/detection_engine/routes/__mocks__/request_context'; -import { createEndpointMetadataServiceTestContextMock } from './services/metadata/mocks'; - -import type { EndpointAuthz } from '../../common/endpoint/types/authz'; -import { EndpointFleetServicesFactory } from './services/fleet'; -import { createLicenseServiceMock } from '../../common/license/mocks'; -import { createFeatureUsageServiceMock } from './services/feature_usage/mocks'; -import { createProductFeaturesServiceMock } from '../lib/product_features_service/mocks'; +} from '../../../common/experimental_features'; +import { requestContextFactoryMock } from '../../request_context_factory.mock'; +import { EndpointMetadataService } from '../services/metadata'; +import type { SecuritySolutionRequestHandlerContextMock } from '../../lib/detection_engine/routes/__mocks__/request_context'; +import { createMockClients } from '../../lib/detection_engine/routes/__mocks__/request_context'; +import { createEndpointMetadataServiceTestContextMock } from '../services/metadata/mocks'; +import type { EndpointAuthz } from '../../../common/endpoint/types/authz'; +import { EndpointFleetServicesFactory } from '../services/fleet'; +import { createLicenseServiceMock } from '../../../common/license/mocks'; +import { createFeatureUsageServiceMock } from '../services/feature_usage/mocks'; +import { createProductFeaturesServiceMock } from '../../lib/product_features_service/mocks'; /** * Creates a mocked EndpointAppContext. */ diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks/utils.mock.ts b/x-pack/plugins/security_solution/server/endpoint/mocks/utils.mock.ts new file mode 100644 index 0000000000000..e4de97523a84a --- /dev/null +++ b/x-pack/plugins/security_solution/server/endpoint/mocks/utils.mock.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import { v4 as uuidV4 } from 'uuid'; +import { BaseDataGenerator } from '../../../common/endpoint/data_generators/base_data_generator'; + +interface ApplyEsClientSearchMockOptions { + esClientMock: ElasticsearchClientMock; + index: string; + response: SearchResponse; + /** + * Mock is to be used only when search is using ES's Point-in-Time + */ + pitUsage?: boolean; +} + +/** + * Generic utility for applying mocks to ES Client mock search method. Any existing mock implementation + * for the `.search()` method will be called if the mock being applied does not match the target + * index, thus this utility can be chained on top of other already applied mock implementations. + * + * This utility also handles search requests using Point In Time. + */ +export const applyEsClientSearchMock = ({ + esClientMock, + index, + response, + pitUsage, +}: ApplyEsClientSearchMockOptions) => { + const priorSearchMockImplementation = esClientMock.search.getMockImplementation(); + const priorOpenPointInTimeImplementation = esClientMock.openPointInTime.getMockImplementation(); + const priorClosePointInTimeImplementation = esClientMock.closePointInTime.getMockImplementation(); + const openedPitIds = new Set(); + + esClientMock.openPointInTime.mockImplementation(async (...args) => { + const options = args[0]; + + if (options.index === index) { + const pitResponse = { id: `mock:pit:${index}:${uuidV4()}` }; + openedPitIds.add(pitResponse.id); + + return pitResponse; + } + + if (priorOpenPointInTimeImplementation) { + return priorOpenPointInTimeImplementation(...args); + } + + return { id: 'mock' }; + }); + + esClientMock.closePointInTime.mockImplementation(async (...args) => { + const closePitResponse = { succeeded: true, num_freed: 1 }; + const options = args[0]; + const pitId = 'id' in options ? options.id : 'body' in options ? options.body?.id : undefined; + + if (pitId) { + if (openedPitIds.has(pitId)) { + openedPitIds.delete(pitId); + return closePitResponse; + } + } + + if (priorClosePointInTimeImplementation) { + return priorClosePointInTimeImplementation(options); + } + + return closePitResponse; + }); + + esClientMock.search.mockImplementation(async (...args) => { + const params = args[0] ?? {}; + const searchReqIndexes = Array.isArray(params.index) ? params.index : [params.index]; + const pit = 'pit' in params ? params.pit : undefined; + + if (params.index && !pitUsage && searchReqIndexes.includes(index)) { + return response; + } else if (pit && pitUsage && openedPitIds.has(pit.id)) { + return response; + } + + if (priorSearchMockImplementation) { + return priorSearchMockImplementation(...args); + } + + return BaseDataGenerator.toEsSearchResponse([]); + }); +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts index cb08cd6cfc30a..a41e651ee3449 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.test.ts @@ -460,6 +460,30 @@ describe('ResponseActionsClientImpl base class', () => { }); describe('#riteActionRequestToEndpointIndex()', () => { + it('should write doc with all expected data', async () => { + indexDocOptions.meta = { one: 1 }; + + await expect( + baseClassMock.writeActionRequestToEndpointIndex(indexDocOptions) + ).resolves.toEqual({ + '@timestamp': expect.any(String), + EndpointActions: { + action_id: expect.any(String), + data: { + command: 'isolate', + comment: 'test comment', + parameters: undefined, + }, + expiration: expect.any(String), + input_type: 'endpoint', + type: 'INPUT_ACTION', + }, + agent: { id: ['one'] }, + meta: { one: 1 }, + user: { id: 'foo' }, + }); + }); + it('should write doc with error when license is not Enterprise', async () => { ( constructorOptions.endpointService.getLicenseService().isEnterprise as jest.Mock @@ -514,6 +538,7 @@ describe('ResponseActionsClientImpl base class', () => { comment: 'some comment', output: undefined, }, + meta: { one: 1 }, }; }); @@ -539,6 +564,7 @@ describe('ResponseActionsClientImpl base class', () => { error: { message: 'test error', }, + meta: { one: 1 }, } as LogsEndpointActionResponse); }); @@ -677,10 +703,20 @@ class MockClassWithExposedProtectedMembers extends ResponseActionsClientImpl { return super.fetchActionDetails(actionId); } - public async writeActionRequestToEndpointIndex( - actionRequest: ResponseActionsClientWriteActionRequestToEndpointIndexOptions - ): Promise { - return super.writeActionRequestToEndpointIndex(actionRequest); + public async writeActionRequestToEndpointIndex< + TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes, + TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput, + TMeta extends {} = {} + >( + actionRequest: ResponseActionsClientWriteActionRequestToEndpointIndexOptions< + TParameters, + TOutputContent, + TMeta + > + ): Promise> { + return super.writeActionRequestToEndpointIndex( + actionRequest + ); } public async writeActionResponseToEndpointIndex< diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts index 5df1079468063..c784c5e5eef5b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/lib/base_response_actions_client.ts @@ -111,19 +111,24 @@ export interface ResponseActionsClientUpdateCasesOptions { actionId: string; } -export type ResponseActionsClientWriteActionRequestToEndpointIndexOptions = - ResponseActionsRequestBody & - Pick & { - command: ResponseActionsApiCommandNames; - actionId?: string; - }; +export type ResponseActionsClientWriteActionRequestToEndpointIndexOptions< + TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes, + TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput, + TMeta extends {} = {} +> = ResponseActionsRequestBody & + Pick & + Pick, 'meta'> & { + command: ResponseActionsApiCommandNames; + actionId?: string; + }; export type ResponseActionsClientWriteActionResponseToEndpointIndexOptions< - TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput + TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput, + TMeta extends {} = {} > = { agentId: LogsEndpointActionResponse['agent']['id']; actionId: string; -} & Pick & +} & Pick, 'error' | 'meta'> & Pick['EndpointActions'], 'data'>; export type ResponseActionsClientValidateRequestResponse = @@ -327,9 +332,17 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient * Creates a Response Action request document in the Endpoint index (`.logs-endpoint.actions-default`) * @protected */ - protected async writeActionRequestToEndpointIndex( - actionRequest: ResponseActionsClientWriteActionRequestToEndpointIndexOptions - ): Promise { + protected async writeActionRequestToEndpointIndex< + TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes, + TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput, + TMeta extends {} = {} + >( + actionRequest: ResponseActionsClientWriteActionRequestToEndpointIndexOptions< + TParameters, + TOutputContent, + TMeta + > + ): Promise> { let errorMsg = String(actionRequest.error ?? '').trim(); if (!errorMsg) { @@ -346,7 +359,7 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient this.notifyUsage(actionRequest.command); - const doc: LogsEndpointAction = { + const doc: LogsEndpointAction = { '@timestamp': new Date().toISOString(), agent: { id: actionRequest.endpoint_ids, @@ -367,6 +380,7 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient user: { id: this.options.username, }, + meta: actionRequest.meta, ...(errorMsg ? { error: { message: errorMsg } } : {}), ...(actionRequest.ruleId && actionRequest.ruleName ? { rule: { id: actionRequest.ruleId, name: actionRequest.ruleName } } @@ -407,15 +421,20 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient protected buildActionResponseEsDoc< // Default type purposely set to empty object in order to ensure proper types are used when calling the method - TOutputContent extends EndpointActionResponseDataOutput = Record + TOutputContent extends EndpointActionResponseDataOutput = Record, + TMeta extends {} = {} >({ actionId, error, agentId, data, - }: ResponseActionsClientWriteActionResponseToEndpointIndexOptions): LogsEndpointActionResponse { + meta, + }: ResponseActionsClientWriteActionResponseToEndpointIndexOptions< + TOutputContent, + TMeta + >): LogsEndpointActionResponse { const timestamp = new Date().toISOString(); - const doc: LogsEndpointActionResponse = { + const doc: LogsEndpointActionResponse = { '@timestamp': timestamp, agent: { id: agentId, @@ -428,6 +447,7 @@ export abstract class ResponseActionsClientImpl implements ResponseActionsClient data, }, error, + meta, }; return doc; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts index b19242e60340b..a0a585b12f7ec 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ResponseActionsClient } from '../lib/types'; +import type { ResponseActionsClient, ProcessPendingActionsMethodOptions } from '../lib/types'; import { responseActionsClientMock } from '../mocks'; import { SentinelOneActionsClient } from './sentinel_one_actions_client'; import { getActionDetailsById as _getActionDetailsById } from '../../action_details_by_id'; @@ -13,10 +13,21 @@ import { ResponseActionsNotSupportedError } from '../errors'; import type { SentinelOneActionsClientOptionsMock } from './mocks'; import { sentinelOneMock } from './mocks'; import { - ENDPOINT_ACTION_RESPONSES_INDEX, + ENDPOINT_ACTION_RESPONSES_INDEX_PATTERN, ENDPOINT_ACTIONS_INDEX, } from '../../../../../../common/endpoint/constants'; import type { NormalizedExternalConnectorClient } from '../../..'; +import { applyEsClientSearchMock } from '../../../../mocks/utils.mock'; +import { SENTINEL_ONE_ACTIVITY_INDEX } from '../../../../../../common'; +import { SentinelOneDataGenerator } from '../../../../../../common/endpoint/data_generators/sentinelone_data_generator'; +import type { + EndpointActionResponse, + LogsEndpointAction, + LogsEndpointActionResponse, + SentinelOneActivityEsDoc, + SentinelOneIsolationRequestMeta, +} from '../../../../../../common/endpoint/types'; +import type { SearchHit } from '@elastic/elasticsearch/lib/api/types'; jest.mock('../../action_details_by_id', () => { const originalMod = jest.requireActual('../../action_details_by_id'); @@ -91,7 +102,7 @@ describe('SentinelOneActionsClient class', () => { it('should write action request and response to endpoint indexes', async () => { await s1ActionsClient.isolate(createS1IsolationOptions()); - expect(classConstructorOptions.esClient.index).toHaveBeenCalledTimes(2); + expect(classConstructorOptions.esClient.index).toHaveBeenCalledTimes(1); expect(classConstructorOptions.esClient.index).toHaveBeenNthCalledWith( 1, { @@ -115,28 +126,17 @@ describe('SentinelOneActionsClient class', () => { }, agent: { id: ['1-2-3'] }, user: { id: 'foo' }, + meta: { + agentId: '1845174760470303882', + agentUUID: '1-2-3', + hostName: 'sentinelone-1460', + }, }, index: ENDPOINT_ACTIONS_INDEX, refresh: 'wait_for', }, { meta: true } ); - expect(classConstructorOptions.esClient.index).toHaveBeenNthCalledWith(2, { - document: { - '@timestamp': expect.any(String), - EndpointActions: { - action_id: expect.any(String), - data: { command: 'isolate' }, - input_type: 'sentinel_one', - started_at: expect.any(String), - completed_at: expect.any(String), - }, - agent: { id: ['1-2-3'] }, - error: undefined, - }, - index: ENDPOINT_ACTION_RESPONSES_INDEX, - refresh: 'wait_for', - }); }); it('should return action details', async () => { @@ -173,7 +173,7 @@ describe('SentinelOneActionsClient class', () => { it('should write action request and response to endpoint indexes', async () => { await s1ActionsClient.release(createS1IsolationOptions()); - expect(classConstructorOptions.esClient.index).toHaveBeenCalledTimes(2); + expect(classConstructorOptions.esClient.index).toHaveBeenCalledTimes(1); expect(classConstructorOptions.esClient.index).toHaveBeenNthCalledWith( 1, { @@ -197,28 +197,17 @@ describe('SentinelOneActionsClient class', () => { }, agent: { id: ['1-2-3'] }, user: { id: 'foo' }, + meta: { + agentId: '1845174760470303882', + agentUUID: '1-2-3', + hostName: 'sentinelone-1460', + }, }, index: ENDPOINT_ACTIONS_INDEX, refresh: 'wait_for', }, { meta: true } ); - expect(classConstructorOptions.esClient.index).toHaveBeenNthCalledWith(2, { - document: { - '@timestamp': expect.any(String), - EndpointActions: { - action_id: expect.any(String), - data: { command: 'unisolate' }, - input_type: 'sentinel_one', - started_at: expect.any(String), - completed_at: expect.any(String), - }, - agent: { id: ['1-2-3'] }, - error: undefined, - }, - index: ENDPOINT_ACTION_RESPONSES_INDEX, - refresh: 'wait_for', - }); }); it('should return action details', async () => { @@ -237,4 +226,218 @@ describe('SentinelOneActionsClient class', () => { expect(classConstructorOptions.casesClient?.attachments.bulkCreate).toHaveBeenCalled(); }); }); + + describe('#processPendingActions()', () => { + let abortController: AbortController; + let processPendingActionsOptions: ProcessPendingActionsMethodOptions; + + beforeEach(() => { + abortController = new AbortController(); + processPendingActionsOptions = { + abortSignal: abortController.signal, + addToQueue: jest.fn(), + }; + }); + + describe('for Isolate and Release', () => { + let actionRequestHits: Array< + SearchHit> + >; + let s1ActivityHits: Array>; + + beforeEach(() => { + const s1DataGenerator = new SentinelOneDataGenerator('seed'); + const actionRequestsSearchResponse = s1DataGenerator.toEsSearchResponse([ + s1DataGenerator.generateActionEsHit({ + agent: { id: 'agent-uuid-1' }, + EndpointActions: { data: { command: 'isolate' } }, + meta: { + agentId: 's1-agent-a', + agentUUID: 'agent-uuid-1', + hostName: 's1-host-name', + }, + }), + ]); + const actionResponsesSearchResponse = s1DataGenerator.toEsSearchResponse< + LogsEndpointActionResponse | EndpointActionResponse + >([]); + const s1ActivitySearchResponse = s1DataGenerator.generateActivityEsSearchResponse([ + s1DataGenerator.generateActivityEsSearchHit({ + sentinel_one: { + activity: { + agent: { + id: 's1-agent-a', + }, + type: 1001, + }, + }, + }), + ]); + + actionRequestHits = actionRequestsSearchResponse.hits.hits; + s1ActivityHits = s1ActivitySearchResponse.hits.hits; + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: ENDPOINT_ACTIONS_INDEX, + response: actionRequestsSearchResponse, + pitUsage: true, + }); + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: ENDPOINT_ACTION_RESPONSES_INDEX_PATTERN, + response: actionResponsesSearchResponse, + }); + + applyEsClientSearchMock({ + esClientMock: classConstructorOptions.esClient, + index: SENTINEL_ONE_ACTIVITY_INDEX, + response: s1ActivitySearchResponse, + }); + }); + + it('should generate action response docs for completed actions', async () => { + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect(processPendingActionsOptions.addToQueue).toHaveBeenCalledWith({ + '@timestamp': expect.any(String), + EndpointActions: { + action_id: '1d6e6796-b0af-496f-92b0-25fcb06db499', + completed_at: expect.any(String), + data: { + command: 'isolate', + }, + input_type: 'sentinel_one', + started_at: expect.any(String), + }, + agent: { + id: 'agent-uuid-1', + }, + error: undefined, + meta: { + activityLogEntryDescription: 'Some description here', + activityLogEntryId: 'd78282bc-e413-468d-9df6-570b91756a6d', + activityLogEntryType: 1001, + elasticDocId: '85f7f003-ebed-4157-b8e6-16ae44fc4be7', + }, + }); + }); + + it('should NOT generate action responses if no activity received from S1', async () => { + s1ActivityHits.length = 0; + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect(processPendingActionsOptions.addToQueue).not.toHaveBeenCalled(); + }); + + it('should complete action as failure S1 agent id meta is missing (edge case)', async () => { + actionRequestHits[0]!._source!.meta!.agentId = ''; + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect(processPendingActionsOptions.addToQueue).toHaveBeenCalledWith( + expect.objectContaining({ + error: { + message: + "Unable to very if action completed. SentinelOne agent id ('meta.agentId') missing on action request document!", + }, + }) + ); + }); + + it('should generate failed isolate response doc if S1 activity `type` is 2010', async () => { + s1ActivityHits[0]._source!.sentinel_one.activity.type = 2010; + s1ActivityHits[0]._source!.sentinel_one.activity.description.primary = + 'Agent SOME_HOST_NAME was unable to disconnect from network.'; + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect(processPendingActionsOptions.addToQueue).toHaveBeenCalledWith( + expect.objectContaining({ + error: { + message: 'Agent SOME_HOST_NAME was unable to disconnect from network.', + }, + }) + ); + }); + + it('should search SentinelOne activity ES index with expected query for isolate', async () => { + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect(classConstructorOptions.esClient.search).toHaveBeenNthCalledWith(4, { + _source: false, + collapse: { + field: 'sentinel_one.activity.agent.id', + inner_hits: { + name: 'first_found', + size: 1, + sort: [{ 'sentinel_one.activity.updated_at': 'asc' }], + }, + }, + index: SENTINEL_ONE_ACTIVITY_INDEX, + query: { + bool: { + minimum_should_match: 1, + must: [{ terms: { 'sentinel_one.activity.type': [1001, 2010] } }], + should: [ + { + bool: { + filter: [ + { term: { 'sentinel_one.activity.agent.id': 's1-agent-a' } }, + { + range: { + 'sentinel_one.activity.updated_at': { gt: expect.any(String) }, + }, + }, + ], + }, + }, + ], + }, + }, + size: 1000, + sort: [{ 'sentinel_one.activity.updated_at': { order: 'asc' } }], + }); + }); + + it('should search SentinelOne activity ES index with expected query for release', async () => { + actionRequestHits[0]._source!.EndpointActions.data.command = 'unisolate'; + await s1ActionsClient.processPendingActions(processPendingActionsOptions); + + expect(classConstructorOptions.esClient.search).toHaveBeenNthCalledWith(4, { + _source: false, + collapse: { + field: 'sentinel_one.activity.agent.id', + inner_hits: { + name: 'first_found', + size: 1, + sort: [{ 'sentinel_one.activity.updated_at': 'asc' }], + }, + }, + index: SENTINEL_ONE_ACTIVITY_INDEX, + query: { + bool: { + minimum_should_match: 1, + must: [{ terms: { 'sentinel_one.activity.type': [1002] } }], + should: [ + { + bool: { + filter: [ + { term: { 'sentinel_one.activity.agent.id': 's1-agent-a' } }, + { + range: { + 'sentinel_one.activity.updated_at': { gt: expect.any(String) }, + }, + }, + ], + }, + }, + ], + }, + }, + size: 1000, + sort: [{ 'sentinel_one.activity.updated_at': { order: 'asc' } }], + }); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts index e674caa69c7e6..cd013128c42cf 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/clients/sentinelone/sentinel_one_actions_client.ts @@ -9,23 +9,44 @@ import { SENTINELONE_CONNECTOR_ID, SUB_ACTION, } from '@kbn/stack-connectors-plugin/common/sentinelone/constants'; +import { groupBy } from 'lodash'; import type { ActionTypeExecutorResult } from '@kbn/actions-plugin/common'; import type { SentinelOneGetAgentsParams, SentinelOneGetAgentsResponse, } from '@kbn/stack-connectors-plugin/common/sentinelone/types'; +import type { + QueryDslQueryContainer, + SearchHit, + SearchRequest, +} from '@elastic/elasticsearch/lib/api/types'; import type { NormalizedExternalConnectorClientExecuteOptions, NormalizedExternalConnectorClient, } from '../lib/normalized_external_connector_client'; +import { SENTINEL_ONE_ACTIVITY_INDEX } from '../../../../../../common'; +import { catchAndWrapError } from '../../../../utils'; import type { CommonResponseActionMethodOptions, ProcessPendingActionsMethodOptions, } from '../../..'; -import type { ResponseActionAgentType } from '../../../../../../common/endpoint/service/response_actions/constants'; +import type { + ResponseActionAgentType, + ResponseActionsApiCommandNames, +} from '../../../../../../common/endpoint/service/response_actions/constants'; import { stringify } from '../../../../utils/stringify'; import { ResponseActionsClientError } from '../errors'; -import type { ActionDetails, LogsEndpointAction } from '../../../../../../common/endpoint/types'; +import type { + ActionDetails, + EndpointActionDataParameterTypes, + EndpointActionResponseDataOutput, + LogsEndpointAction, + LogsEndpointActionResponse, + SentinelOneActionRequestCommonMeta, + SentinelOneActivityEsDoc, + SentinelOneIsolationRequestMeta, + SentinelOneIsolationResponseMeta, +} from '../../../../../../common/endpoint/types'; import type { IsolationRouteRequestBody } from '../../../../../../common/api/endpoint'; import type { ResponseActionsClientOptions, @@ -48,18 +69,41 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { connectorActions.setup(SENTINELONE_CONNECTOR_ID); } - protected async writeActionRequestToEndpointIndex( - actionRequest: Omit - ): Promise { - const agentId = actionRequest.endpoint_ids[0]; - const agentDetails = await this.getAgentDetails(agentId); - - return super.writeActionRequestToEndpointIndex({ + protected async writeActionRequestToEndpointIndex< + TParameters extends EndpointActionDataParameterTypes = EndpointActionDataParameterTypes, + TOutputContent extends EndpointActionResponseDataOutput = EndpointActionResponseDataOutput, + TMeta extends {} = {} + >( + actionRequest: ResponseActionsClientWriteActionRequestToEndpointIndexOptions< + TParameters, + TOutputContent, + TMeta + > + ): Promise< + LogsEndpointAction + > { + const agentUUID = actionRequest.endpoint_ids[0]; + const agentDetails = await this.getAgentDetails(agentUUID); + + const doc = await super.writeActionRequestToEndpointIndex< + TParameters, + TOutputContent, + TMeta & SentinelOneActionRequestCommonMeta + >({ ...actionRequest, hosts: { - [agentId]: { name: agentDetails.computerName }, + [agentUUID]: { name: agentDetails.computerName }, }, + meta: { + // Add common meta data + agentUUID, + agentId: agentDetails.id, + hostName: agentDetails.computerName, + ...(actionRequest.meta ?? {}), + } as TMeta & SentinelOneActionRequestCommonMeta, }); + + return doc; } /** @@ -101,7 +145,9 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { } /** Gets agent details directly from SentinelOne */ - private async getAgentDetails(id: string): Promise { + private async getAgentDetails( + agentUUID: string + ): Promise { const executeOptions: NormalizedExternalConnectorClientExecuteOptions< SentinelOneGetAgentsParams, SUB_ACTION @@ -109,23 +155,33 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { params: { subAction: SUB_ACTION.GET_AGENTS, subActionParams: { - uuid: id, + uuid: agentUUID, }, }, }; - const s1ApiResponse: SentinelOneGetAgentsResponse | undefined = ( - (await this.connectorActionsClient.execute( + let s1ApiResponse: SentinelOneGetAgentsResponse | undefined; + + try { + const response = (await this.connectorActionsClient.execute( executeOptions - )) as ActionTypeExecutorResult - ).data; + )) as ActionTypeExecutorResult; - this.log.debug( - `Response for SentinelOne agent id [${id}] returned:\n${stringify(s1ApiResponse)}` - ); + this.log.debug( + `Response for SentinelOne agent id [${agentUUID}] returned:\n${stringify(response)}` + ); + + s1ApiResponse = response.data; + } catch (err) { + throw new ResponseActionsClientError( + `Error while attempting to retrieve SentinelOne host with agent id [${agentUUID}]`, + 500, + err + ); + } if (!s1ApiResponse || !s1ApiResponse.data[0]) { - throw new ResponseActionsClientError(`SentinelOne agent id [${id}] not found`, 404); + throw new ResponseActionsClientError(`SentinelOne agent id [${agentUUID}] not found`, 404); } return s1ApiResponse.data[0]; @@ -152,7 +208,11 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { actionRequest: IsolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise { - const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions = { + const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions< + undefined, + {}, + SentinelOneIsolationRequestMeta + > = { ...actionRequest, ...this.getMethodOptions(options), command: 'isolate', @@ -192,16 +252,6 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { comment: reqIndexOptions.comment, }); - if (!actionRequestDoc.error) { - await this.writeActionResponseToEndpointIndex({ - actionId: actionRequestDoc.EndpointActions.action_id, - agentId: actionRequestDoc.agent.id, - data: { - command: actionRequestDoc.EndpointActions.data.command, - }, - }); - } - return this.fetchActionDetails(actionRequestDoc.EndpointActions.action_id); } @@ -209,7 +259,11 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { actionRequest: IsolationRouteRequestBody, options: CommonResponseActionMethodOptions = {} ): Promise { - const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions = { + const reqIndexOptions: ResponseActionsClientWriteActionRequestToEndpointIndexOptions< + undefined, + {}, + SentinelOneIsolationRequestMeta + > = { ...actionRequest, ...this.getMethodOptions(options), command: 'unisolate', @@ -249,16 +303,6 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { comment: reqIndexOptions.comment, }); - if (!actionRequestDoc.error) { - await this.writeActionResponseToEndpointIndex({ - actionId: actionRequestDoc.EndpointActions.action_id, - agentId: actionRequestDoc.agent.id, - data: { - command: actionRequestDoc.EndpointActions.data.command, - }, - }); - } - return this.fetchActionDetails(actionRequestDoc.EndpointActions.action_id); } @@ -266,11 +310,231 @@ export class SentinelOneActionsClient extends ResponseActionsClientImpl { abortSignal, addToQueue, }: ProcessPendingActionsMethodOptions): Promise { - // TODO:PT implement resolving of pending S1 actions - // if (abortSignal.aborted) { - // return; - // } - // Dev test entry below - // await this.getAgentDetails('123').catch(() => {}); + if (abortSignal.aborted) { + return; + } + + for await (const pendingActions of this.fetchAllPendingActions()) { + if (abortSignal.aborted) { + return; + } + + const pendingActionsByType = groupBy(pendingActions, 'EndpointActions.data.command'); + + for (const [actionType, typePendingActions] of Object.entries(pendingActionsByType)) { + if (abortSignal.aborted) { + return; + } + + switch (actionType as ResponseActionsApiCommandNames) { + case 'isolate': + case 'unisolate': + { + const isolationResponseDocs = await this.checkPendingIsolateOrReleaseActions( + typePendingActions as Array< + LogsEndpointAction + >, + actionType as 'isolate' | 'unisolate' + ); + if (isolationResponseDocs.length) { + addToQueue(...isolationResponseDocs); + } + } + break; + } + } + } + } + + /** + * Checks if the provided Isolate or Unisolate actions are complete and if so, then it builds the Response + * document for them and returns it. (NOTE: the response is NOT written to ES - only returned) + * @param actionRequests + * @param command + * @private + */ + private async checkPendingIsolateOrReleaseActions( + actionRequests: Array>, + command: ResponseActionsApiCommandNames & ('isolate' | 'unisolate') + ): Promise { + const completedResponses: LogsEndpointActionResponse[] = []; + const actionsByAgentId: { + [s1AgentId: string]: Array< + LogsEndpointAction + >; + } = {}; + const warnings: string[] = []; + + // Create the `OR` clause that filters for each agent id and an updated date of greater than the date when + // the isolate request was created + const agentListQuery: QueryDslQueryContainer[] = actionRequests.reduce((acc, action) => { + const s1AgentId = action.meta?.agentId; + + if (s1AgentId) { + if (!actionsByAgentId[s1AgentId]) { + actionsByAgentId[s1AgentId] = []; + } + + actionsByAgentId[s1AgentId].push(action); + + acc.push({ + bool: { + filter: [ + { term: { 'sentinel_one.activity.agent.id': s1AgentId } }, + { range: { 'sentinel_one.activity.updated_at': { gt: action['@timestamp'] } } }, + ], + }, + }); + } else { + // This is an edge case and should never happen. But just in case :-) + warnings.push( + `${command} response action ID [${action.EndpointActions.action_id}] missing SentinelOne agent ID, thus unable to check on it's status. Forcing it to complete as failure.` + ); + + completedResponses.push( + this.buildActionResponseEsDoc<{}, SentinelOneIsolationResponseMeta>({ + actionId: action.EndpointActions.action_id, + agentId: Array.isArray(action.agent.id) ? action.agent.id[0] : action.agent.id, + data: { command }, + error: { + message: `Unable to very if action completed. SentinelOne agent id ('meta.agentId') missing on action request document!`, + }, + }) + ); + } + + return acc; + }, [] as QueryDslQueryContainer[]); + + if (agentListQuery.length > 0) { + const query: QueryDslQueryContainer = { + bool: { + must: [ + { + terms: { + // Activity Types can be retrieved from S1 via API: `/web/api/v2.1/activities/types` + 'sentinel_one.activity.type': + command === 'isolate' + ? [ + // { + // "id": 1001 + // "action": "Agent Disconnected From Network", + // "descriptionTemplate": "Agent {{ computer_name }} was disconnected from network.", + // }, + 1001, + + // { + // "id": 2010 + // "action": "Agent Mitigation Report Quarantine Network Failed", + // "descriptionTemplate": "Agent {{ computer_name }} was unable to disconnect from network.", + // }, + 2010, + ] + : [ + // { + // "id": 1002 + // "action": "Agent Reconnected To Network", + // "descriptionTemplate": "Agent {{ computer_name }} was connected to network.", + // }, + 1002, + ], + }, + }, + ], + should: agentListQuery, + minimum_should_match: 1, + }, + }; + + const searchRequestOptions: SearchRequest = { + index: SENTINEL_ONE_ACTIVITY_INDEX, + query, + // There may be many documents for each host/agent, so we collapse it and only get back the + // first one that came in after the isolate request was sent + collapse: { + field: 'sentinel_one.activity.agent.id', + inner_hits: { + name: 'first_found', + size: 1, + sort: [{ 'sentinel_one.activity.updated_at': 'asc' }], + }, + }, + // we don't need the source. The document will be stored in `inner_hits.first_found` + // due to use of `collapse + _source: false, + sort: [{ 'sentinel_one.activity.updated_at': { order: 'asc' } }], + size: 1000, + }; + + this.log.debug( + `searching for ${command} responses from [${SENTINEL_ONE_ACTIVITY_INDEX}] index with:\n${stringify( + searchRequestOptions, + 15 + )}` + ); + + const searchResults = await this.options.esClient + .search(searchRequestOptions) + .catch(catchAndWrapError); + + this.log.debug( + `Search results for SentinelOne ${command} activity documents:\n${stringify(searchResults)}` + ); + + for (const searchResultHit of searchResults.hits.hits) { + const isolateActivityResponseDoc = searchResultHit.inner_hits?.first_found.hits + .hits[0] as SearchHit; + + if (isolateActivityResponseDoc && isolateActivityResponseDoc._source) { + const s1ActivityData = isolateActivityResponseDoc._source.sentinel_one.activity; + + const elasticDocId = isolateActivityResponseDoc._id; + const s1AgentId = s1ActivityData.agent.id; + const activityLogEntryId = s1ActivityData.id; + const activityLogEntryType = s1ActivityData.type; + const activityLogEntryDescription = s1ActivityData.description.primary; + + for (const actionRequest of actionsByAgentId[s1AgentId]) { + completedResponses.push( + this.buildActionResponseEsDoc<{}, SentinelOneIsolationResponseMeta>({ + actionId: actionRequest.EndpointActions.action_id, + agentId: Array.isArray(actionRequest.agent.id) + ? actionRequest.agent.id[0] + : actionRequest.agent.id, + data: { command }, + error: + activityLogEntryType === 2010 && command === 'isolate' + ? { + message: + activityLogEntryDescription ?? + `Action failed. SentinelOne activity log entry [${activityLogEntryId}] has a 'type' value of 2010 indicating a failure to disconnect`, + } + : undefined, + meta: { + elasticDocId, + activityLogEntryId, + activityLogEntryType, + activityLogEntryDescription, + }, + }) + ); + } + } + } + } else { + this.log.debug(`Nothing to search for. List of agents IDs is empty.`); + } + + this.log.debug( + `${completedResponses.length} ${command} action responses generated:\n${stringify( + completedResponses + )}` + ); + + if (warnings.length > 0) { + this.log.warn(warnings.join('\n')); + } + + return completedResponses; } } diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts index d15fb6ad2fe3d..9911ae781c20f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/mocks.ts @@ -10,6 +10,7 @@ import type { ElasticsearchClientMock } from '@kbn/core/server/mocks'; import { AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common'; import { Readable } from 'stream'; import type { TransportRequestOptions } from '@elastic/transport'; +import { applyEsClientSearchMock } from '../../mocks/utils.mock'; import type { HapiReadableStream } from '../../../types'; import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; import { FleetActionGenerator } from '../../../../common/endpoint/data_generators/fleet_action_generator'; @@ -104,26 +105,22 @@ export const applyActionsEsSearchMock = ( LogsEndpointActionResponse | EndpointActionResponse > = createActionResponsesEsSearchResultsMock() ) => { - const priorSearchMockImplementation = esClient.search.getMockImplementation(); - - esClient.search.mockImplementation(async (...args) => { - const params = args[0] ?? {}; - const indexes = Array.isArray(params.index) ? params.index : [params.index]; - - if (indexes.includes(ENDPOINT_ACTIONS_INDEX)) { - return actionRequests; - } else if ( - indexes.includes(AGENT_ACTIONS_RESULTS_INDEX) || - indexes.includes(ENDPOINT_ACTION_RESPONSES_INDEX_PATTERN) - ) { - return actionResponses; - } + applyEsClientSearchMock({ + esClientMock: esClient, + index: ENDPOINT_ACTIONS_INDEX, + response: actionRequests, + }); - if (priorSearchMockImplementation) { - return priorSearchMockImplementation(...args); - } + applyEsClientSearchMock({ + esClientMock: esClient, + index: AGENT_ACTIONS_RESULTS_INDEX, + response: actionResponses, + }); - return new EndpointActionGenerator().toEsSearchResponse([]); + applyEsClientSearchMock({ + esClientMock: esClient, + index: ENDPOINT_ACTION_RESPONSES_INDEX_PATTERN, + response: actionResponses, }); }; diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts b/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts index 42dd639c9b5f2..05bd638956f89 100644 --- a/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts +++ b/x-pack/plugins/security_solution/server/endpoint/utils/queue_processor.ts @@ -133,8 +133,10 @@ export class QueueProcessor { * Adds an update to the queue */ public addToQueue(...data: T[]) { - this.queue.push(...data); - this.processQueue(); + if (data.length > 0) { + this.queue.push(...data); + this.processQueue(); + } } /** diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts index c2cade2c0146b..0e9564f566e9a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.test.ts @@ -194,6 +194,27 @@ describe('rule_converters', () => { ); }); + test('should accept new_terms alerts suppression params', () => { + const patchParams = { + alert_suppression: { + group_by: ['agent.name'], + duration: { value: 4, unit: 'h' as const }, + missing_fields_strategy: 'suppress' as const, + }, + }; + const rule = getNewTermsRuleParams(); + const patchedParams = patchTypeSpecificSnakeToCamel(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + alertSuppression: { + groupBy: ['agent.name'], + missingFieldsStrategy: 'suppress', + duration: { value: 4, unit: 'h' }, + }, + }) + ); + }); + test('should accept machine learning params when existing rule type is machine learning', () => { const patchParams = { anomaly_threshold: 5, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts index 8efad70feeb00..418689914889d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts @@ -199,6 +199,7 @@ export const typeSpecificSnakeToCamel = ( filters: params.filters, language: params.language ?? 'kuery', dataViewId: params.data_view_id, + alertSuppression: convertAlertSuppressionToCamel(params.alert_suppression), }; } default: { @@ -345,6 +346,8 @@ const patchNewTermsParams = ( filters: params.filters ?? existingRule.filters, newTermsFields: params.new_terms_fields ?? existingRule.newTermsFields, historyWindowStart: params.history_window_start ?? existingRule.historyWindowStart, + alertSuppression: + convertAlertSuppressionToCamel(params.alert_suppression) ?? existingRule.alertSuppression, }; }; @@ -643,6 +646,7 @@ export const typeSpecificCamelToSnake = ( filters: params.filters, language: params.language, data_view_id: params.dataViewId, + alert_suppression: convertAlertSuppressionToSnake(params.alertSuppression), }; } default: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts index deb853335bf24..dfb19d3b35ba5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts @@ -268,6 +268,7 @@ export const NewTermsSpecificRuleParams = z.object({ filters: RuleFilterArray.optional(), language: KqlQueryLanguage, dataViewId: DataViewId.optional(), + alertSuppression: AlertSuppressionCamel.optional(), }); export type NewTermsRuleParams = BaseRuleParams & NewTermsSpecificRuleParams; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/build_esql_search_request.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/build_esql_search_request.ts index 54ddd9b818c23..9b04127613d22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/build_esql_search_request.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/build_esql_search_request.ts @@ -7,6 +7,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { Filter } from '@kbn/es-query'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import type { RuleFilterArray, TimestampOverride, @@ -56,6 +57,7 @@ export const buildEsqlSearchRequest = ({ // we limit size of the response to maxAlertNumber + 1 // ES|QL currently does not support pagination and returns 10,000 results query: `${query} | limit ${size + 1}`, + version: ESQL_LATEST_VERSION, filter: { bool: { filter: requestFilter, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/bulk_create_suppressed_alerts_in_memory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/bulk_create_suppressed_alerts_in_memory.ts new file mode 100644 index 0000000000000..8d39fcccee62e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/bulk_create_suppressed_alerts_in_memory.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SuppressedAlertService } from '@kbn/rule-registry-plugin/server'; + +import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas'; +import type { + SearchAfterAndBulkCreateParams, + SearchAfterAndBulkCreateReturnType, + WrapSuppressedHits, +} from '../types'; + +import type { AlertSuppressionCamel } from '../../../../../common/api/detection_engine/model/rule_schema'; +import { DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY } from '../../../../../common/detection_engine/constants'; +import { AlertSuppressionMissingFieldsStrategyEnum } from '../../../../../common/api/detection_engine/model/rule_schema'; + +import { executeBulkCreateAlerts } from '../utils/bulk_create_suppressed_alerts_in_memory'; +import type { + BaseFieldsLatest, + WrappedFieldsLatest, + NewTermsFieldsLatest, +} from '../../../../../common/api/detection_engine/model/alerts'; +import { partitionMissingFieldsEvents } from '../utils/partition_missing_fields_events'; +import type { EventsAndTerms } from './types'; +import type { ExperimentalFeatures } from '../../../../../common'; + +interface SearchAfterAndBulkCreateSuppressedAlertsParams extends SearchAfterAndBulkCreateParams { + wrapSuppressedHits: WrapSuppressedHits; + alertTimestampOverride: Date | undefined; + alertWithSuppression: SuppressedAlertService; + alertSuppression?: AlertSuppressionCamel; +} +export interface BulkCreateSuppressedAlertsParams + extends Pick< + SearchAfterAndBulkCreateSuppressedAlertsParams, + | 'bulkCreate' + | 'services' + | 'ruleExecutionLogger' + | 'tuple' + | 'alertSuppression' + | 'alertWithSuppression' + | 'alertTimestampOverride' + > { + wrapHits: ( + events: EventsAndTerms[] + ) => Array>; + wrapSuppressedHits: ( + events: EventsAndTerms[] + ) => Array< + WrappedFieldsLatest + >; + eventsAndTerms: EventsAndTerms[]; + toReturn: SearchAfterAndBulkCreateReturnType; + experimentalFeatures: ExperimentalFeatures | undefined; +} +/** + * wraps, bulk create and suppress alerts in memory, also takes care of missing fields logic. + * If parameter alertSuppression.missingFieldsStrategy configured not to be suppressed, regular alerts will be created for such events without suppression + * This function is similar to x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_suppressed_alerts_in_memory.ts, but + * it operates with new terms specific eventsAndTerms{@link EventsAndTerms} parameter property, instead of regular events as common utility + */ +export const bulkCreateSuppressedNewTermsAlertsInMemory = async ({ + eventsAndTerms, + wrapHits, + wrapSuppressedHits, + toReturn, + bulkCreate, + services, + ruleExecutionLogger, + tuple, + alertSuppression, + alertWithSuppression, + alertTimestampOverride, + experimentalFeatures, +}: BulkCreateSuppressedAlertsParams) => { + const suppressOnMissingFields = + (alertSuppression?.missingFieldsStrategy ?? DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY) === + AlertSuppressionMissingFieldsStrategyEnum.suppress; + + let suppressibleEvents = eventsAndTerms; + let unsuppressibleWrappedDocs: Array> = []; + + if (!suppressOnMissingFields) { + const partitionedEvents = partitionMissingFieldsEvents( + eventsAndTerms, + alertSuppression?.groupBy || [], + ['event'] + ); + + unsuppressibleWrappedDocs = wrapHits(partitionedEvents[1]); + + suppressibleEvents = partitionedEvents[0]; + } + + const suppressibleWrappedDocs = wrapSuppressedHits(suppressibleEvents); + + return executeBulkCreateAlerts({ + suppressibleWrappedDocs, + unsuppressibleWrappedDocs, + toReturn, + bulkCreate, + services, + ruleExecutionLogger, + tuple, + alertSuppression, + alertWithSuppression, + alertTimestampOverride, + experimentalFeatures, + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts index 398e73d6861f8..42ffec0b6236c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/create_new_terms_alert_type.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isObject } from 'lodash'; +import { isObject, chunk } from 'lodash'; import { NEW_TERMS_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; @@ -16,13 +16,16 @@ import type { CreateRuleOptions, SecurityAlertType } from '../types'; import { singleSearchAfter } from '../utils/single_search_after'; import { getFilter } from '../utils/get_filter'; import { wrapNewTermsAlerts } from './wrap_new_terms_alerts'; -import type { EventsAndTerms } from './wrap_new_terms_alerts'; +import { wrapSuppressedNewTermsAlerts } from './wrap_suppressed_new_terms_alerts'; +import { bulkCreateSuppressedNewTermsAlertsInMemory } from './bulk_create_suppressed_alerts_in_memory'; +import type { EventsAndTerms } from './types'; import type { RecentTermsAggResult, DocFetchAggResult, NewTermsAggResult, CreateAlertsHook, } from './build_new_terms_aggregation'; +import type { NewTermsFieldsLatest } from '../../../../../common/api/detection_engine/model/alerts'; import { buildRecentTermsAgg, buildNewTermsAgg, @@ -35,14 +38,17 @@ import { createSearchAfterReturnType, getUnprocessedExceptionsWarnings, getMaxSignalsWarning, + getSuppressionMaxSignalsWarning, } from '../utils/utils'; import { createEnrichEventsFunction } from '../utils/enrichments'; +import { getIsAlertSuppressionActive } from '../utils/get_is_alert_suppression_active'; import { multiTermsComposite } from './multi_terms_composite'; +import type { GenericBulkCreateResponse } from '../utils/bulk_create_with_suppression'; export const createNewTermsAlertType = ( createOptions: CreateRuleOptions ): SecurityAlertType => { - const { logger } = createOptions; + const { logger, licensing } = createOptions; return { id: NEW_TERMS_RULE_TYPE_ID, name: 'New Terms Rule', @@ -104,6 +110,8 @@ export const createNewTermsAlertType = ( alertTimestampOverride, publicBaseUrl, inputIndexFields, + experimentalFeatures, + alertWithSuppression, }, services, params, @@ -137,6 +145,11 @@ export const createNewTermsAlertType = ( name: 'historyWindowStart', }); + const isAlertSuppressionActive = await getIsAlertSuppressionActive({ + alertSuppression: params.alertSuppression, + licensing, + isFeatureDisabled: !experimentalFeatures?.alertSuppressionForNewTermsRuleEnabled, + }); let afterKey; const result = createSearchAfterReturnType(); @@ -191,6 +204,32 @@ export const createNewTermsAlertType = ( const bucketsForField = searchResultWithAggs.aggregations.new_terms.buckets; const createAlertsHook: CreateAlertsHook = async (aggResult) => { + const wrapHits = (eventsAndTerms: EventsAndTerms[]) => + wrapNewTermsAlerts({ + eventsAndTerms, + spaceId, + completeRule, + mergeStrategy, + indicesToQuery: inputIndex, + alertTimestampOverride, + ruleExecutionLogger, + publicBaseUrl, + }); + + const wrapSuppressedHits = (eventsAndTerms: EventsAndTerms[]) => + wrapSuppressedNewTermsAlerts({ + eventsAndTerms, + spaceId, + completeRule, + mergeStrategy, + indicesToQuery: inputIndex, + alertTimestampOverride, + ruleExecutionLogger, + publicBaseUrl, + primaryTimestamp, + secondaryTimestamp, + }); + const eventsAndTerms: EventsAndTerms[] = ( aggResult?.aggregations?.new_terms.buckets ?? [] ).map((bucket) => { @@ -201,27 +240,61 @@ export const createNewTermsAlertType = ( }; }); - const wrappedAlerts = wrapNewTermsAlerts({ - eventsAndTerms, - spaceId, - completeRule, - mergeStrategy, - indicesToQuery: inputIndex, - alertTimestampOverride, - ruleExecutionLogger, - publicBaseUrl, - }); + let bulkCreateResult: Omit< + GenericBulkCreateResponse, + 'suppressedItemsCount' + > = { + errors: [], + success: true, + enrichmentDuration: '0', + bulkCreateDuration: '0', + createdItemsCount: 0, + createdItems: [], + alertsWereTruncated: false, + }; - const bulkCreateResult = await bulkCreate( - wrappedAlerts, - params.maxSignals - result.createdSignalsCount, - createEnrichEventsFunction({ - services, - logger: ruleExecutionLogger, - }) - ); + // wrap and create alerts by chunks + // large number of matches, processed in possibly 10,000 size of events and terms + // can significantly affect Kibana performance + const eventAndTermsChunks = chunk(eventsAndTerms, 5 * params.maxSignals); - addToSearchAfterReturn({ current: result, next: bulkCreateResult }); + for (let i = 0; i < eventAndTermsChunks.length; i++) { + const eventAndTermsChunk = eventAndTermsChunks[i]; + + if (isAlertSuppressionActive) { + bulkCreateResult = await bulkCreateSuppressedNewTermsAlertsInMemory({ + eventsAndTerms: eventAndTermsChunk, + toReturn: result, + wrapHits, + bulkCreate, + services, + ruleExecutionLogger, + tuple, + alertSuppression: params.alertSuppression, + wrapSuppressedHits, + alertTimestampOverride, + alertWithSuppression, + experimentalFeatures, + }); + } else { + const wrappedAlerts = wrapHits(eventAndTermsChunk); + + bulkCreateResult = await bulkCreate( + wrappedAlerts, + params.maxSignals - result.createdSignalsCount, + createEnrichEventsFunction({ + services, + logger: ruleExecutionLogger, + }) + ); + + addToSearchAfterReturn({ current: result, next: bulkCreateResult }); + } + + if (bulkCreateResult.alertsWereTruncated) { + break; + } + } return bulkCreateResult; }; @@ -229,7 +302,7 @@ export const createNewTermsAlertType = ( // separate route for multiple new terms // it uses paging through composite aggregation if (params.newTermsFields.length > 1) { - await multiTermsComposite({ + const bulkCreateResult = await multiTermsComposite({ filterArgs, buckets: bucketsForField, params, @@ -241,7 +314,12 @@ export const createNewTermsAlertType = ( runOpts: execOptions.runOpts, afterKey, createAlertsHook, + isAlertSuppressionActive, }); + + if (bulkCreateResult?.alertsWereTruncated) { + break; + } } else { // PHASE 2: Take the page of results from Phase 1 and determine if each term exists in the history window. // The aggregation filters out buckets for terms that exist prior to `tuple.from`, so the buckets in the @@ -326,7 +404,11 @@ export const createNewTermsAlertType = ( const bulkCreateResult = await createAlertsHook(docFetchResultWithAggs); if (bulkCreateResult.alertsWereTruncated) { - result.warningMessages.push(getMaxSignalsWarning()); + result.warningMessages.push( + isAlertSuppressionActive + ? getSuppressionMaxSignalsWarning() + : getMaxSignalsWarning() + ); break; } } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/multi_terms_composite.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/multi_terms_composite.ts index dc33626bbd535..572443566f8d8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/multi_terms_composite.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/multi_terms_composite.ts @@ -21,8 +21,9 @@ import type { CompositeNewTermsAggResult, CreateAlertsHook, } from './build_new_terms_aggregation'; - -import { getMaxSignalsWarning } from '../utils/utils'; +import type { NewTermsFieldsLatest } from '../../../../../common/api/detection_engine/model/alerts'; +import { getMaxSignalsWarning, getSuppressionMaxSignalsWarning } from '../utils/utils'; +import type { GenericBulkCreateResponse } from '../utils/bulk_create_with_suppression'; import type { RuleServices, SearchAfterAndBulkCreateReturnType, RunOpts } from '../types'; @@ -47,6 +48,7 @@ interface MultiTermsCompositeArgsBase { runOpts: RunOpts; afterKey: Record | undefined; createAlertsHook: CreateAlertsHook; + isAlertSuppressionActive: boolean; } interface MultiTermsCompositeArgs extends MultiTermsCompositeArgsBase { @@ -72,7 +74,10 @@ const multiTermsCompositeNonRetryable = async ({ afterKey, createAlertsHook, batchSize, -}: MultiTermsCompositeArgs) => { + isAlertSuppressionActive, +}: MultiTermsCompositeArgs): Promise< + Omit, 'suppressedItemsCount'> | undefined +> => { const { ruleExecutionLogger, tuple, @@ -183,15 +188,15 @@ const multiTermsCompositeNonRetryable = async ({ const bulkCreateResult = await createAlertsHook(docFetchResultWithAggs); if (bulkCreateResult.alertsWereTruncated) { - result.warningMessages.push(getMaxSignalsWarning()); - return result; + result.warningMessages.push( + isAlertSuppressionActive ? getSuppressionMaxSignalsWarning() : getMaxSignalsWarning() + ); + return bulkCreateResult; } } internalAfterKey = batch[batch.length - 1]?.key; } - - return result; }; /** @@ -199,10 +204,14 @@ const multiTermsCompositeNonRetryable = async ({ * We will try to reduce it in twice per each request, three times, up until 125 * Per ES documentation, max_clause_count min value is 1,000 - so with 125 we should be able execute query below max_clause_count value */ -export const multiTermsComposite = async (args: MultiTermsCompositeArgsBase): Promise => { +export const multiTermsComposite = async ( + args: MultiTermsCompositeArgsBase +): Promise< + Omit, 'suppressedItemsCount'> | undefined +> => { let retryBatchSize = BATCH_SIZE; const ruleExecutionLogger = args.runOpts.ruleExecutionLogger; - await pRetry( + return pRetry( async (retryCount) => { try { const res = await multiTermsCompositeNonRetryable({ ...args, batchSize: retryBatchSize }); @@ -218,7 +227,7 @@ export const multiTermsComposite = async (args: MultiTermsCompositeArgsBase): Pr ].some((errMessage) => e.message.includes(errMessage)) ) { args.result.errors.push(e.message); - return args.result; + return; } retryBatchSize = retryBatchSize / 2; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/types.ts new file mode 100644 index 0000000000000..270ffefb6b448 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type * as estypes from '@elastic/elasticsearch/lib/api/types'; +import type { SignalSource } from '../types'; + +export interface EventsAndTerms { + event: estypes.SearchHit; + newTerms: Array; +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_suppressed_new_terms_alerts.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_suppressed_new_terms_alerts.test.ts new file mode 100644 index 0000000000000..e92bbad598941 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_suppressed_new_terms_alerts.test.ts @@ -0,0 +1,142 @@ +/* + * 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 { cloneDeep } from 'lodash'; + +import { + ALERT_URL, + ALERT_UUID, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_INSTANCE_ID, + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_END, +} from '@kbn/rule-data-utils'; +import { ALERT_NEW_TERMS } from '../../../../../common/field_maps/field_names'; +import { getCompleteRuleMock, getNewTermsRuleParams } from '../../rule_schema/mocks'; +import { ruleExecutionLogMock } from '../../rule_monitoring/mocks'; +import { sampleDocNoSortIdWithTimestamp } from '../__mocks__/es_results'; +import { wrapSuppressedNewTermsAlerts } from './wrap_suppressed_new_terms_alerts'; + +const ruleExecutionLogger = ruleExecutionLogMock.forExecutors.create(); + +const docId = 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71'; +const publicBaseUrl = 'http://somekibanabaseurl.com'; + +const alertSuppression = { + groupBy: ['source.ip'], +}; + +const completeRule = getCompleteRuleMock(getNewTermsRuleParams()); +completeRule.ruleParams.alertSuppression = alertSuppression; + +describe('wrapSuppressedNewTermsAlerts', () => { + test('should create an alert with the correct _id from a document and suppression fields', () => { + const doc = sampleDocNoSortIdWithTimestamp(docId); + const alerts = wrapSuppressedNewTermsAlerts({ + eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.1'] }], + spaceId: 'default', + mergeStrategy: 'missingFields', + completeRule, + indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + alertTimestampOverride: undefined, + ruleExecutionLogger, + publicBaseUrl, + primaryTimestamp: '@timestamp', + }); + + expect(alerts[0]._id).toEqual('3b67aa2ebdc628afc98febc65082d2d83a116d79'); + expect(alerts[0]._source[ALERT_UUID]).toEqual('3b67aa2ebdc628afc98febc65082d2d83a116d79'); + expect(alerts[0]._source[ALERT_NEW_TERMS]).toEqual(['127.0.0.1']); + expect(alerts[0]._source[ALERT_URL]).toContain( + 'http://somekibanabaseurl.com/app/security/alerts/redirect/3b67aa2ebdc628afc98febc65082d2d83a116d79?index=.alerts-security.alerts-default' + ); + expect(alerts[0]._source[ALERT_SUPPRESSION_DOCS_COUNT]).toEqual(0); + expect(alerts[0]._source[ALERT_INSTANCE_ID]).toEqual( + '1bf77f90e72d76d9335ad0ce356340a3d9833f96' + ); + expect(alerts[0]._source[ALERT_SUPPRESSION_TERMS]).toEqual([ + { field: 'source.ip', value: ['127.0.0.1'] }, + ]); + expect(alerts[0]._source[ALERT_SUPPRESSION_START]).toBeDefined(); + expect(alerts[0]._source[ALERT_SUPPRESSION_END]).toBeDefined(); + }); + + test('should create an alert with a different _id if suppression field is different', () => { + const completeRuleCloned = cloneDeep(completeRule); + completeRuleCloned.ruleParams.alertSuppression = { + groupBy: ['someKey'], + }; + const doc = sampleDocNoSortIdWithTimestamp(docId); + const alerts = wrapSuppressedNewTermsAlerts({ + eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.1'] }], + spaceId: 'default', + mergeStrategy: 'missingFields', + completeRule: completeRuleCloned, + indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + alertTimestampOverride: undefined, + ruleExecutionLogger, + publicBaseUrl, + primaryTimestamp: '@timestamp', + }); + + expect(alerts[0]._id).toEqual('3e0436a03b735af12d6e5358cb36d2c3b39425a8'); + expect(alerts[0]._source[ALERT_UUID]).toEqual('3e0436a03b735af12d6e5358cb36d2c3b39425a8'); + expect(alerts[0]._source[ALERT_URL]).toContain( + 'http://somekibanabaseurl.com/app/security/alerts/redirect/3e0436a03b735af12d6e5358cb36d2c3b39425a8?index=.alerts-security.alerts-default' + ); + expect(alerts[0]._source[ALERT_SUPPRESSION_DOCS_COUNT]).toEqual(0); + expect(alerts[0]._source[ALERT_INSTANCE_ID]).toEqual( + '01e43acf431fd232bbe230ac523a5d5d1e8a2787' + ); + expect(alerts[0]._source[ALERT_SUPPRESSION_TERMS]).toEqual([ + { field: 'someKey', value: ['someValue'] }, + ]); + }); + + test('should create an alert with a different _id if the space is different', () => { + const doc = sampleDocNoSortIdWithTimestamp(docId); + const alerts = wrapSuppressedNewTermsAlerts({ + eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.1'] }], + spaceId: 'otherSpace', + mergeStrategy: 'missingFields', + completeRule, + indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + alertTimestampOverride: undefined, + ruleExecutionLogger, + publicBaseUrl, + primaryTimestamp: '@timestamp', + }); + + expect(alerts[0]._id).toEqual('f8a029df9c99e245dc83977153a0612178f3d2e8'); + expect(alerts[0]._source[ALERT_UUID]).toEqual('f8a029df9c99e245dc83977153a0612178f3d2e8'); + expect(alerts[0]._source[ALERT_URL]).toContain( + 'http://somekibanabaseurl.com/s/otherSpace/app/security/alerts/redirect/f8a029df9c99e245dc83977153a0612178f3d2e8?index=.alerts-security.alerts-otherSpace' + ); + }); + + test('should create an alert with a different _id if the newTerms array is different', () => { + const doc = sampleDocNoSortIdWithTimestamp(docId); + const alerts = wrapSuppressedNewTermsAlerts({ + eventsAndTerms: [{ event: doc, newTerms: ['127.0.0.2'] }], + spaceId: 'otherSpace', + mergeStrategy: 'missingFields', + completeRule, + indicesToQuery: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + alertTimestampOverride: undefined, + ruleExecutionLogger, + publicBaseUrl, + primaryTimestamp: '@timestamp', + }); + + expect(alerts[0]._id).toEqual('cb8684ec72592346d32839b1838e4f4080dc052e'); + expect(alerts[0]._source[ALERT_UUID]).toEqual('cb8684ec72592346d32839b1838e4f4080dc052e'); + expect(alerts[0]._source[ALERT_NEW_TERMS]).toEqual(['127.0.0.2']); + expect(alerts[0]._source[ALERT_URL]).toContain( + 'http://somekibanabaseurl.com/s/otherSpace/app/security/alerts/redirect/cb8684ec72592346d32839b1838e4f4080dc052e?index=.alerts-security.alerts-otherSpace' + ); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_suppressed_new_terms_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_suppressed_new_terms_alerts.ts new file mode 100644 index 0000000000000..6aeb48e30fd99 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/new_terms/wrap_suppressed_new_terms_alerts.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type * as estypes from '@elastic/elasticsearch/lib/api/types'; +import objectHash from 'object-hash'; +import { TIMESTAMP } from '@kbn/rule-data-utils'; +import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas'; +import type { + BaseFieldsLatest, + NewTermsFieldsLatest, + WrappedFieldsLatest, +} from '../../../../../common/api/detection_engine/model/alerts'; +import { ALERT_NEW_TERMS } from '../../../../../common/field_maps/field_names'; +import type { ConfigType } from '../../../../config'; +import type { CompleteRule, NewTermsRuleParams } from '../../rule_schema'; +import { buildReasonMessageForNewTermsAlert } from '../utils/reason_formatters'; +import { getSuppressionAlertFields, getSuppressionTerms } from '../utils'; +import type { SignalSource } from '../types'; +import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring'; +import { buildBulkBody } from '../factories/utils/build_bulk_body'; + +export interface EventsAndTerms { + event: estypes.SearchHit; + newTerms: Array; +} + +export const wrapSuppressedNewTermsAlerts = ({ + eventsAndTerms, + spaceId, + completeRule, + mergeStrategy, + indicesToQuery, + alertTimestampOverride, + ruleExecutionLogger, + publicBaseUrl, + primaryTimestamp, + secondaryTimestamp, +}: { + eventsAndTerms: EventsAndTerms[]; + spaceId: string | null | undefined; + completeRule: CompleteRule; + mergeStrategy: ConfigType['alertMergeStrategy']; + indicesToQuery: string[]; + alertTimestampOverride: Date | undefined; + ruleExecutionLogger: IRuleExecutionLogForExecutors; + publicBaseUrl: string | undefined; + primaryTimestamp: string; + secondaryTimestamp?: string; +}): Array> => { + return eventsAndTerms.map((eventAndTerms) => { + const event = eventAndTerms.event; + + const suppressionTerms = getSuppressionTerms({ + alertSuppression: completeRule?.ruleParams?.alertSuppression, + fields: event.fields, + }); + + const instanceId = objectHash([suppressionTerms, completeRule.alertId, spaceId]); + + const id = objectHash([ + eventAndTerms.event._index, + eventAndTerms.event._id, + String(eventAndTerms.event._version), + `${spaceId}:${completeRule.alertId}`, + eventAndTerms.newTerms, + suppressionTerms, + ]); + + const baseAlert: BaseFieldsLatest = buildBulkBody( + spaceId, + completeRule, + event, + mergeStrategy, + [], + true, + buildReasonMessageForNewTermsAlert, + indicesToQuery, + alertTimestampOverride, + ruleExecutionLogger, + id, + publicBaseUrl + ); + + return { + _id: id, + _index: '', + _source: { + ...baseAlert, + [ALERT_NEW_TERMS]: eventAndTerms.newTerms, + ...getSuppressionAlertFields({ + primaryTimestamp, + secondaryTimestamp, + fields: event.fields, + suppressionTerms, + fallbackTimestamp: baseAlert[TIMESTAMP], + instanceId, + }), + }, + }; + }); +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_suppressed_alerts_in_memory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_suppressed_alerts_in_memory.ts new file mode 100644 index 0000000000000..94d7003c4eb13 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_suppressed_alerts_in_memory.ts @@ -0,0 +1,188 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { SuppressedAlertService } from '@kbn/rule-registry-plugin/server'; + +import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas'; +import type { + SearchAfterAndBulkCreateParams, + SearchAfterAndBulkCreateReturnType, + WrapSuppressedHits, + SignalSourceHit, +} from '../types'; +import { MAX_SIGNALS_SUPPRESSION_MULTIPLIER } from '../constants'; +import { addToSearchAfterReturn } from './utils'; +import type { AlertSuppressionCamel } from '../../../../../common/api/detection_engine/model/rule_schema'; +import { DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY } from '../../../../../common/detection_engine/constants'; +import { partitionMissingFieldsEvents } from './partition_missing_fields_events'; +import { AlertSuppressionMissingFieldsStrategyEnum } from '../../../../../common/api/detection_engine/model/rule_schema'; +import { createEnrichEventsFunction } from './enrichments'; +import { bulkCreateWithSuppression } from './bulk_create_with_suppression'; +import type { ExperimentalFeatures } from '../../../../../common'; + +import type { + BaseFieldsLatest, + WrappedFieldsLatest, +} from '../../../../../common/api/detection_engine/model/alerts'; + +interface SearchAfterAndBulkCreateSuppressedAlertsParams extends SearchAfterAndBulkCreateParams { + wrapSuppressedHits: WrapSuppressedHits; + alertTimestampOverride: Date | undefined; + alertWithSuppression: SuppressedAlertService; + alertSuppression?: AlertSuppressionCamel; +} +export interface BulkCreateSuppressedAlertsParams + extends Pick< + SearchAfterAndBulkCreateSuppressedAlertsParams, + | 'wrapHits' + | 'bulkCreate' + | 'services' + | 'buildReasonMessage' + | 'ruleExecutionLogger' + | 'tuple' + | 'alertSuppression' + | 'wrapSuppressedHits' + | 'alertWithSuppression' + | 'alertTimestampOverride' + > { + enrichedEvents: SignalSourceHit[]; + toReturn: SearchAfterAndBulkCreateReturnType; + experimentalFeatures?: ExperimentalFeatures; +} +/** + * wraps, bulk create and suppress alerts in memory, also takes care of missing fields logic. + * If parameter alertSuppression.missingFieldsStrategy configured not to be suppressed, regular alerts will be created for such events without suppression + */ +export const bulkCreateSuppressedAlertsInMemory = async ({ + enrichedEvents, + toReturn, + wrapHits, + bulkCreate, + services, + buildReasonMessage, + ruleExecutionLogger, + tuple, + alertSuppression, + wrapSuppressedHits, + alertWithSuppression, + alertTimestampOverride, + experimentalFeatures, +}: BulkCreateSuppressedAlertsParams) => { + const suppressOnMissingFields = + (alertSuppression?.missingFieldsStrategy ?? DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY) === + AlertSuppressionMissingFieldsStrategyEnum.suppress; + + let suppressibleEvents = enrichedEvents; + let unsuppressibleWrappedDocs: Array> = []; + + if (!suppressOnMissingFields) { + const partitionedEvents = partitionMissingFieldsEvents( + enrichedEvents, + alertSuppression?.groupBy || [] + ); + + unsuppressibleWrappedDocs = wrapHits(partitionedEvents[1], buildReasonMessage); + suppressibleEvents = partitionedEvents[0]; + } + + const suppressibleWrappedDocs = wrapSuppressedHits(suppressibleEvents, buildReasonMessage); + + return executeBulkCreateAlerts({ + suppressibleWrappedDocs, + unsuppressibleWrappedDocs, + toReturn, + bulkCreate, + services, + ruleExecutionLogger, + tuple, + alertSuppression, + alertWithSuppression, + alertTimestampOverride, + experimentalFeatures, + }); +}; + +export interface ExecuteBulkCreateAlertsParams + extends Pick< + SearchAfterAndBulkCreateSuppressedAlertsParams, + | 'bulkCreate' + | 'services' + | 'ruleExecutionLogger' + | 'tuple' + | 'alertSuppression' + | 'alertWithSuppression' + | 'alertTimestampOverride' + > { + unsuppressibleWrappedDocs: Array>; + suppressibleWrappedDocs: Array>; + toReturn: SearchAfterAndBulkCreateReturnType; + experimentalFeatures?: ExperimentalFeatures; +} + +/** + * creates alerts in ES, both suppressed and unsuppressed + */ +export const executeBulkCreateAlerts = async < + T extends SuppressionFieldsLatest & BaseFieldsLatest +>({ + unsuppressibleWrappedDocs, + suppressibleWrappedDocs, + toReturn, + bulkCreate, + services, + ruleExecutionLogger, + tuple, + alertSuppression, + alertWithSuppression, + alertTimestampOverride, + experimentalFeatures, +}: ExecuteBulkCreateAlertsParams) => { + // max signals for suppression includes suppressed and created alerts + // this allows to lift max signals limitation to higher value + // and can detects events beyond default max_signals value + const suppressionMaxSignals = MAX_SIGNALS_SUPPRESSION_MULTIPLIER * tuple.maxSignals; + const suppressionDuration = alertSuppression?.duration; + + const suppressionWindow = suppressionDuration + ? `now-${suppressionDuration.value}${suppressionDuration.unit}` + : tuple.from.toISOString(); + + if (unsuppressibleWrappedDocs.length) { + const unsuppressedResult = await bulkCreate( + unsuppressibleWrappedDocs, + tuple.maxSignals - toReturn.createdSignalsCount, + createEnrichEventsFunction({ + services, + logger: ruleExecutionLogger, + }) + ); + + addToSearchAfterReturn({ current: toReturn, next: unsuppressedResult }); + } + + const bulkCreateResult = await bulkCreateWithSuppression({ + alertWithSuppression, + ruleExecutionLogger, + wrappedDocs: suppressibleWrappedDocs, + services, + suppressionWindow, + alertTimestampOverride, + isSuppressionPerRuleExecution: !suppressionDuration, + maxAlerts: tuple.maxSignals - toReturn.createdSignalsCount, + experimentalFeatures, + }); + + addToSearchAfterReturn({ current: toReturn, next: bulkCreateResult }); + + const alertsWereTruncated = + (toReturn.suppressedAlertsCount ?? 0) + toReturn.createdSignalsCount >= suppressionMaxSignals || + toReturn.createdSignalsCount >= tuple.maxSignals; + + return { + ...bulkCreateResult, + alertsWereTruncated, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_with_suppression.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_with_suppression.ts index 5e7d0e9d88454..35d55ec7f39d2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_with_suppression.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/bulk_create_with_suppression.ts @@ -21,6 +21,7 @@ import type { } from '../../../../../common/api/detection_engine/model/alerts'; import type { RuleServices } from '../types'; import { createEnrichEventsFunction } from './enrichments'; +import type { ExperimentalFeatures } from '../../../../../common'; export interface GenericBulkCreateResponse { success: boolean; @@ -30,6 +31,7 @@ export interface GenericBulkCreateResponse { suppressedItemsCount: number; createdItems: Array & { _id: string; _index: string }>; errors: string[]; + alertsWereTruncated: boolean; } export const bulkCreateWithSuppression = async < @@ -42,6 +44,8 @@ export const bulkCreateWithSuppression = async < suppressionWindow, alertTimestampOverride, isSuppressionPerRuleExecution, + maxAlerts, + experimentalFeatures, }: { alertWithSuppression: SuppressedAlertService; ruleExecutionLogger: IRuleExecutionLogForExecutors; @@ -50,6 +54,8 @@ export const bulkCreateWithSuppression = async < suppressionWindow: string; alertTimestampOverride: Date | undefined; isSuppressionPerRuleExecution?: boolean; + maxAlerts?: number; + experimentalFeatures?: ExperimentalFeatures; }): Promise> => { if (wrappedDocs.length === 0) { return { @@ -60,6 +66,7 @@ export const bulkCreateWithSuppression = async < createdItemsCount: 0, suppressedItemsCount: 0, createdItems: [], + alertsWereTruncated: false, }; } @@ -75,7 +82,7 @@ export const bulkCreateWithSuppression = async < const enrichAlertsWrapper: typeof enrichAlerts = async (alerts, params) => { enrichmentsTimeStart = performance.now(); try { - const enrichedAlerts = await enrichAlerts(alerts, params); + const enrichedAlerts = await enrichAlerts(alerts, params, experimentalFeatures); return enrichedAlerts; } catch (error) { ruleExecutionLogger.error(`Alerts enrichment failed: ${error}`); @@ -85,17 +92,19 @@ export const bulkCreateWithSuppression = async < } }; - const { createdAlerts, errors, suppressedAlerts } = await alertWithSuppression( - wrappedDocs.map((doc) => ({ - _id: doc._id, - // `fields` should have already been merged into `doc._source` - _source: doc._source, - })), - suppressionWindow, - enrichAlertsWrapper, - alertTimestampOverride, - isSuppressionPerRuleExecution - ); + const { createdAlerts, errors, suppressedAlerts, alertsWereTruncated } = + await alertWithSuppression( + wrappedDocs.map((doc) => ({ + _id: doc._id, + // `fields` should have already been merged into `doc._source` + _source: doc._source, + })), + suppressionWindow, + enrichAlertsWrapper, + alertTimestampOverride, + isSuppressionPerRuleExecution, + maxAlerts + ); const end = performance.now(); @@ -111,6 +120,7 @@ export const bulkCreateWithSuppression = async < createdItemsCount: createdAlerts.length, createdItems: createdAlerts, suppressedItemsCount: suppressedAlerts.length, + alertsWereTruncated, }; } else { return { @@ -121,6 +131,7 @@ export const bulkCreateWithSuppression = async < createdItemsCount: createdAlerts.length, createdItems: createdAlerts, suppressedItemsCount: suppressedAlerts.length, + alertsWereTruncated, }; } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_is_alert_suppression_active.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_is_alert_suppression_active.ts new file mode 100644 index 0000000000000..846fddb46b90e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/get_is_alert_suppression_active.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; + +import { firstValueFrom } from 'rxjs'; + +import type { AlertSuppressionCamel } from '../../../../../common/api/detection_engine/model/rule_schema'; + +interface GetIsAlertSuppressionActiveParams { + alertSuppression: AlertSuppressionCamel | undefined; + isFeatureDisabled: boolean | undefined; + licensing: LicensingPluginSetup; +} + +/** + * checks if alert suppression is active: + * - rule should have alert suppression config + * - feature flag should not be disabled + * - license should be platinum + */ +export const getIsAlertSuppressionActive = async ({ + licensing, + alertSuppression, + isFeatureDisabled = false, +}: GetIsAlertSuppressionActiveParams) => { + if (isFeatureDisabled) { + return false; + } + + const isAlertSuppressionConfigured = Boolean(alertSuppression?.groupBy?.length); + + if (!isAlertSuppressionConfigured) { + return false; + } + + const license = await firstValueFrom(licensing.license$); + const hasPlatinumLicense = license.hasAtLeast('platinum'); + + return hasPlatinumLicense; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts index 8f23da386f5c7..ded7990075010 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/index.ts @@ -28,3 +28,4 @@ export const createResultObject = (state: TState) export * from './get_list_client'; export * from './validate_mutated_params'; export * from './build_timestamp_runtime_mapping'; +export * from './suppression_utils'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/partition_missing_fields_events.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/partition_missing_fields_events.test.ts index 8e515cf46e1c6..dfee32a058ba6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/partition_missing_fields_events.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/partition_missing_fields_events.test.ts @@ -56,6 +56,63 @@ describe('partitionMissingFieldsEvents', () => { ], ]); }); + it('should partition when fields objects located in event property', () => { + expect( + partitionMissingFieldsEvents( + [ + { + event: { + fields: { + 'agent.host': 'host-1', + 'agent.type': ['test-1', 'test-2'], + 'agent.version': 2, + }, + _id: '1', + _index: 'index-0', + }, + }, + { + event: { + fields: { + 'agent.host': 'host-1', + 'agent.type': ['test-1', 'test-2'], + }, + _id: '1', + _index: 'index-0', + }, + }, + ], + ['agent.host', 'agent.type', 'agent.version'], + ['event'] + ) + ).toEqual([ + [ + { + event: { + fields: { + 'agent.host': 'host-1', + 'agent.type': ['test-1', 'test-2'], + 'agent.version': 2, + }, + _id: '1', + _index: 'index-0', + }, + }, + ], + [ + { + event: { + fields: { + 'agent.host': 'host-1', + 'agent.type': ['test-1', 'test-2'], + }, + _id: '1', + _index: 'index-0', + }, + }, + ], + ]); + }); it('should partition if two fields are empty', () => { expect( partitionMissingFieldsEvents( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/partition_missing_fields_events.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/partition_missing_fields_events.ts index 6506a291da561..f86a969dc60c3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/partition_missing_fields_events.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/partition_missing_fields_events.ts @@ -6,6 +6,7 @@ */ import pick from 'lodash/pick'; +import get from 'lodash/get'; import partition from 'lodash/partition'; import type { SignalSourceHit } from '../types'; @@ -15,16 +16,21 @@ import type { SignalSourceHit } from '../types'; * 1. first one, where no suppressed by field has empty value * 2. where any of fields is empty */ -export const partitionMissingFieldsEvents = ( - events: SignalSourceHit[], - suppressedBy: string[] = [] -): SignalSourceHit[][] => { +export const partitionMissingFieldsEvents = < + T extends SignalSourceHit | { event: SignalSourceHit } +>( + events: T[], + suppressedBy: string[] = [], + // path to fields property within event object. At this point, it can be in root of event object or within event key + fieldsPath: ['event'] | [] = [] +): T[][] => { return partition(events, (event) => { if (suppressedBy.length === 0) { return true; } + const eventFields = get(event, [...fieldsPath, 'fields']); const hasMissingFields = - Object.keys(pick(event.fields, suppressedBy)).length < suppressedBy.length; + Object.keys(pick(eventFields, suppressedBy)).length < suppressedBy.length; return !hasMissingFields; }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create_suppressed_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create_suppressed_alerts.ts index 73f7bfd4a6ba4..0a89dec4bd251 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create_suppressed_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/search_after_bulk_create_suppressed_alerts.ts @@ -7,20 +7,14 @@ import type { SuppressedAlertService } from '@kbn/rule-registry-plugin/server'; -import { bulkCreateWithSuppression } from './bulk_create_with_suppression'; -import { addToSearchAfterReturn, getSuppressionMaxSignalsWarning } from './utils'; +import { getSuppressionMaxSignalsWarning } from './utils'; import type { SearchAfterAndBulkCreateParams, SearchAfterAndBulkCreateReturnType, WrapSuppressedHits, } from '../types'; -import { MAX_SIGNALS_SUPPRESSION_MULTIPLIER } from '../constants'; - -import { createEnrichEventsFunction } from './enrichments'; -import { AlertSuppressionMissingFieldsStrategyEnum } from '../../../../../common/api/detection_engine/model/rule_schema'; import type { AlertSuppressionCamel } from '../../../../../common/api/detection_engine/model/rule_schema'; -import { DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY } from '../../../../../common/detection_engine/constants'; -import { partitionMissingFieldsEvents } from './partition_missing_fields_events'; + interface SearchAfterAndBulkCreateSuppressedAlertsParams extends SearchAfterAndBulkCreateParams { wrapSuppressedHits: WrapSuppressedHits; alertTimestampOverride: Date | undefined; @@ -30,6 +24,7 @@ interface SearchAfterAndBulkCreateSuppressedAlertsParams extends SearchAfterAndB import type { SearchAfterAndBulkCreateFactoryParams } from './search_after_bulk_create_factory'; import { searchAfterAndBulkCreateFactory } from './search_after_bulk_create_factory'; +import { bulkCreateSuppressedAlertsInMemory } from './bulk_create_suppressed_alerts_in_memory'; /** * search_after through documents and re-index using bulk endpoint @@ -55,62 +50,20 @@ export const searchAfterAndBulkCreateSuppressedAlerts = async ( enrichedEvents, toReturn, }) => { - // max signals for suppression includes suppressed and created alerts - // this allows to lift max signals limitation to higher value - // and can detects threats beyond default max_signals value - const suppressionMaxSignals = MAX_SIGNALS_SUPPRESSION_MULTIPLIER * tuple.maxSignals; - - const suppressionDuration = alertSuppression?.duration; - const suppressionWindow = suppressionDuration - ? `now-${suppressionDuration.value}${suppressionDuration.unit}` - : tuple.from.toISOString(); - - const suppressOnMissingFields = - (alertSuppression?.missingFieldsStrategy ?? DEFAULT_SUPPRESSION_MISSING_FIELDS_STRATEGY) === - AlertSuppressionMissingFieldsStrategyEnum.suppress; - - let suppressibleEvents = enrichedEvents; - if (!suppressOnMissingFields) { - const partitionedEvents = partitionMissingFieldsEvents( - enrichedEvents, - alertSuppression?.groupBy || [] - ); - - const wrappedDocs = wrapHits(partitionedEvents[1], buildReasonMessage); - suppressibleEvents = partitionedEvents[0]; - - const unsuppressedResult = await bulkCreate( - wrappedDocs, - tuple.maxSignals - toReturn.createdSignalsCount, - createEnrichEventsFunction({ - services, - logger: ruleExecutionLogger, - }) - ); - - addToSearchAfterReturn({ current: toReturn, next: unsuppressedResult }); - } - - const wrappedDocs = wrapSuppressedHits(suppressibleEvents, buildReasonMessage); - - const bulkCreateResult = await bulkCreateWithSuppression({ - alertWithSuppression, - ruleExecutionLogger, - wrappedDocs, + return bulkCreateSuppressedAlertsInMemory({ + wrapHits, + bulkCreate, services, - suppressionWindow, + buildReasonMessage, + ruleExecutionLogger, + tuple, + alertSuppression, + wrapSuppressedHits, + alertWithSuppression, alertTimestampOverride, - isSuppressionPerRuleExecution: !suppressionDuration, + enrichedEvents, + toReturn, }); - - addToSearchAfterReturn({ current: toReturn, next: bulkCreateResult }); - - return { - ...bulkCreateResult, - alertsWereTruncated: - (toReturn.suppressedAlertsCount ?? 0) + toReturn.createdSignalsCount >= - suppressionMaxSignals || toReturn.createdSignalsCount >= tuple.maxSignals, - }; }; return searchAfterAndBulkCreateFactory({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/suppression_utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/suppression_utils.test.ts new file mode 100644 index 0000000000000..745dd08977520 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/suppression_utils.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_INSTANCE_ID, + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_END, +} from '@kbn/rule-data-utils'; + +import { getSuppressionTerms, getSuppressionAlertFields } from './suppression_utils'; + +describe('getSuppressionAlertFields', () => { + const suppressionTerms = [ + { + field: 'agent.name', + value: ['agent-0'], + }, + ]; + const instanceId = 'mock-id'; + const fallbackTimestamp = '2020-10-28T06:30:00.000Z'; + const expectedTimestamp = '2022-02-24T06:30:00.000Z'; + + it('should return suppression fields', () => { + expect( + getSuppressionAlertFields({ + primaryTimestamp: '@timestamp', + fields: { '@timestamp': expectedTimestamp }, + suppressionTerms, + instanceId, + fallbackTimestamp, + }) + ).toEqual({ + [ALERT_SUPPRESSION_TERMS]: suppressionTerms, + [ALERT_SUPPRESSION_START]: new Date(expectedTimestamp), + [ALERT_SUPPRESSION_END]: new Date(expectedTimestamp), + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + [ALERT_INSTANCE_ID]: instanceId, + }); + }); + it('should set suppression boundaries from secondary timestamp field', () => { + expect( + getSuppressionAlertFields({ + primaryTimestamp: '@timestamp', + secondaryTimestamp: 'event.ingested', + fields: { 'event.ingested': expectedTimestamp }, + suppressionTerms, + instanceId, + fallbackTimestamp, + }) + ).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_START]: new Date(expectedTimestamp), + [ALERT_SUPPRESSION_END]: new Date(expectedTimestamp), + }) + ); + }); + it('should set suppression boundaries from fallback timestamp', () => { + expect( + getSuppressionAlertFields({ + primaryTimestamp: '@timestamp', + secondaryTimestamp: 'event.ingested', + fields: {}, + suppressionTerms, + instanceId, + fallbackTimestamp, + }) + ).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_START]: new Date(fallbackTimestamp), + [ALERT_SUPPRESSION_END]: new Date(fallbackTimestamp), + }) + ); + }); +}); + +describe('getSuppressionTerms', () => { + it('should return suppression terms', () => { + expect( + getSuppressionTerms({ + alertSuppression: { + groupBy: ['host.name'], + }, + fields: { 'host.name': 'localhost-1' }, + }) + ).toEqual([{ field: 'host.name', value: 'localhost-1' }]); + }); + it('should return suppression terms array when fields do not have matches', () => { + expect( + getSuppressionTerms({ + alertSuppression: { + groupBy: ['host.name'], + }, + fields: { 'host.ip': '127.0.0.1' }, + }) + ).toEqual([{ field: 'host.name', value: null }]); + }); + it('should return sorted suppression terms array value', () => { + expect( + getSuppressionTerms({ + alertSuppression: { + groupBy: ['host.name'], + }, + fields: { 'host.name': ['localhost-2', 'localhost-1'] }, + }) + ).toEqual([{ field: 'host.name', value: ['localhost-1', 'localhost-2'] }]); + }); + it('should return multiple suppression terms', () => { + expect( + getSuppressionTerms({ + alertSuppression: { + groupBy: ['host.name', 'host.ip'], + }, + fields: { 'host.name': ['localhost-1'], 'agent.name': 'test', 'host.ip': '127.0.0.1' }, + }) + ).toEqual([ + { field: 'host.name', value: ['localhost-1'] }, + { field: 'host.ip', value: '127.0.0.1' }, + ]); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/suppression_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/suppression_utils.ts new file mode 100644 index 0000000000000..167f22dc1d52b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/suppression_utils.ts @@ -0,0 +1,86 @@ +/* + * 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 pick from 'lodash/pick'; +import get from 'lodash/get'; +import sortBy from 'lodash/sortBy'; + +import { + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_INSTANCE_ID, + ALERT_SUPPRESSION_TERMS, + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_END, +} from '@kbn/rule-data-utils'; +import type { AlertSuppressionCamel } from '../../../../../common/api/detection_engine/model/rule_schema'; +interface SuppressionTerm { + field: string; + value: string[] | number[] | null; +} + +/** + * returns an object containing the standard suppression fields (ALERT_INSTANCE_ID, ALERT_SUPPRESSION_TERMS, etc), with corresponding values populated from the `fields` parameter. + */ +export const getSuppressionAlertFields = ({ + primaryTimestamp, + secondaryTimestamp, + fields, + suppressionTerms, + fallbackTimestamp, + instanceId, +}: { + fields: Record | undefined; + primaryTimestamp: string; + secondaryTimestamp?: string; + suppressionTerms: SuppressionTerm[]; + fallbackTimestamp: string; + instanceId: string; +}) => { + const suppressionTime = new Date( + get(fields, primaryTimestamp) ?? + (secondaryTimestamp && get(fields, secondaryTimestamp)) ?? + fallbackTimestamp + ); + + const suppressionFields = { + [ALERT_INSTANCE_ID]: instanceId, + [ALERT_SUPPRESSION_TERMS]: suppressionTerms, + [ALERT_SUPPRESSION_START]: suppressionTime, + [ALERT_SUPPRESSION_END]: suppressionTime, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }; + + return suppressionFields; +}; + +/** + * returns an array of {@link SuppressionTerm}s by retrieving the appropriate field values based on the provided alertSuppression configuration + */ +export const getSuppressionTerms = ({ + alertSuppression, + fields, +}: { + fields: Record | undefined; + alertSuppression: AlertSuppressionCamel | undefined; +}): SuppressionTerm[] => { + const suppressedBy = alertSuppression?.groupBy ?? []; + + const suppressedProps = pick(fields, suppressedBy) as Record< + string, + string[] | number[] | undefined + >; + const suppressionTerms = suppressedBy.map((field) => { + const value = suppressedProps[field] ?? null; + const sortedValue = Array.isArray(value) ? (sortBy(value) as string[] | number[]) : value; + return { + field, + value: sortedValue, + }; + }); + + return suppressionTerms; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts index b8ccacb9e515f..c461873510543 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/utils/wrap_suppressed_alerts.ts @@ -6,19 +6,9 @@ */ import objectHash from 'object-hash'; -import pick from 'lodash/pick'; -import get from 'lodash/get'; -import sortBy from 'lodash/sortBy'; import type { SuppressionFieldsLatest } from '@kbn/rule-registry-plugin/common/schemas'; -import { - ALERT_SUPPRESSION_DOCS_COUNT, - ALERT_INSTANCE_ID, - ALERT_SUPPRESSION_TERMS, - ALERT_SUPPRESSION_START, - ALERT_SUPPRESSION_END, - TIMESTAMP, -} from '@kbn/rule-data-utils'; +import { TIMESTAMP } from '@kbn/rule-data-utils'; import type { SignalSourceHit } from '../types'; import type { @@ -29,6 +19,7 @@ import type { ConfigType } from '../../../../config'; import type { CompleteRule, ThreatRuleParams } from '../../rule_schema'; import type { IRuleExecutionLogForExecutors } from '../../rule_monitoring'; import { buildBulkBody } from '../factories/utils/build_bulk_body'; +import { getSuppressionAlertFields, getSuppressionTerms } from './suppression_utils'; import type { BuildReasonMessage } from './reason_formatters'; @@ -62,20 +53,10 @@ export const wrapSuppressedAlerts = ({ primaryTimestamp: string; secondaryTimestamp?: string; }): Array> => { - const suppressedBy = completeRule?.ruleParams?.alertSuppression?.groupBy ?? []; - return events.map((event) => { - const suppressedProps = pick(event.fields, suppressedBy) as Record< - string, - string[] | number[] | undefined - >; - const suppressionTerms = suppressedBy.map((field) => { - const value = suppressedProps[field] ?? null; - const sortedValue = Array.isArray(value) ? (sortBy(value) as string[] | number[]) : value; - return { - field, - value: sortedValue, - }; + const suppressionTerms = getSuppressionTerms({ + alertSuppression: completeRule?.ruleParams?.alertSuppression, + fields: event.fields, }); const id = objectHash([ @@ -102,22 +83,19 @@ export const wrapSuppressedAlerts = ({ publicBaseUrl ); - const suppressionTime = new Date( - get(event.fields, primaryTimestamp) ?? - (secondaryTimestamp && get(event.fields, secondaryTimestamp)) ?? - baseAlert[TIMESTAMP] - ); - return { _id: id, _index: '', _source: { ...baseAlert, - [ALERT_SUPPRESSION_TERMS]: suppressionTerms, - [ALERT_SUPPRESSION_START]: suppressionTime, - [ALERT_SUPPRESSION_END]: suppressionTime, - [ALERT_SUPPRESSION_DOCS_COUNT]: 0, - [ALERT_INSTANCE_ID]: instanceId, + ...getSuppressionAlertFields({ + primaryTimestamp, + secondaryTimestamp, + fields: event.fields, + suppressionTerms, + fallbackTimestamp: baseAlert[TIMESTAMP], + instanceId, + }), }, }; }); diff --git a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx index 2c3f613db50a0..649b74cb14ee1 100644 --- a/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_management_app.test.tsx @@ -58,7 +58,8 @@ async function mountApp(basePath: string, pathname: string, spaceId?: string) { element: container, setBreadcrumbs, history: scopedHistoryMock.create({ pathname }), - theme$: themeServiceMock.createTheme$(), + theme: coreStart.theme, + theme$: themeServiceMock.createTheme$(), // needed as a deprecated field in ManagementAppMountParams }); return { unmount, container, setBreadcrumbs, docTitle: coreStart.chrome.docTitle }; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.test.ts index 1d7096d20140e..ce50dcb2edede 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.test.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.test.ts @@ -67,6 +67,7 @@ describe('fetchEsqlQuery', () => { }, }, "query": "from test", + "version": "2024.04.01", } `); }); @@ -94,6 +95,7 @@ describe('fetchEsqlQuery', () => { }, }, "query": "from test | limit 100", + "version": "2024.04.01", } `); }); diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts index 87ae2c1123547..4b527f529874e 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_esql_query.ts @@ -6,6 +6,7 @@ */ import { parseAggregationResults } from '@kbn/triggers-actions-ui-plugin/common'; +import { ESQL_LATEST_VERSION } from '@kbn/esql-utils'; import { SharePluginStart } from '@kbn/share-plugin/server'; import { IScopedClusterClient, Logger } from '@kbn/core/server'; import { OnlyEsqlQueryRuleParams } from '../types'; @@ -90,6 +91,7 @@ export const getEsqlQuery = ( const query = { query: alertLimit ? `${params.esqlQuery.esql} | limit ${alertLimit}` : params.esqlQuery.esql, + version: ESQL_LATEST_VERSION, filter: { bool: { filter: rangeFilter, diff --git a/x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx b/x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx index 59a6d3808ff81..6503cc7763469 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx +++ b/x-pack/plugins/stack_connectors/public/connector_types/lib/test_utils.tsx @@ -6,14 +6,12 @@ */ import React, { useCallback } from 'react'; -import { of } from 'rxjs'; import { I18nProvider } from '@kbn/i18n-react'; import { EuiButton } from '@elastic/eui'; import { Form, useForm, FormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { act } from 'react-dom/test-utils'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { ConnectorServices } from '@kbn/triggers-actions-ui-plugin/public/types'; import { TriggersAndActionsUiServices } from '@kbn/triggers-actions-ui-plugin/public'; @@ -93,13 +91,10 @@ export interface AppMockRenderer { export const createAppMockRenderer = (): AppMockRenderer => { const services = createStartServicesMock(); - const theme$ = of({ darkMode: false }); const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => ( - - {children} - + {children} ); AppWrapper.displayName = 'AppWrapper'; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 51644cdd3af9a..8be33ddcdb086 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2242,13 +2242,12 @@ "dataViews.unableWriteLabel": "Impossible d'écrire la vue de données ! Actualisez la page pour obtenir la dernière version de cette vue de données.", "discover.advancedSettings.disableDocumentExplorerDescription": "Désactivez cette option pour utiliser le nouveau {documentExplorerDocs} au lieu de la vue classique. l'explorateur de documents offre un meilleur tri des données, des colonnes redimensionnables et une vue en plein écran.", "discover.advancedSettings.discover.showFieldStatisticsDescription": "Activez le {fieldStatisticsDocs} pour afficher des détails tels que les valeurs minimale et maximale d'un champ numérique ou une carte d'un champ géographique. Cette fonctionnalité est en version bêta et susceptible d'être modifiée.", - "discover.advancedSettings.discover.showMultifieldsDescription": "Détermine si les {multiFields} doivent s'afficher dans la fenêtre de document étendue. Dans la plupart des cas, les champs multiples sont les mêmes que les champs d'origine. Cette option est uniquement disponible lorsque le paramètre `searchFieldsFromSource` est désactivé.", - "discover.advancedSettings.enableESQLDescription": "{technicalPreviewLabel} Cette fonctionnalité en préversion technique est à un stade hautement expérimental ; ne pas s'y fier pour les recherches enregistrées, ni pour les visualisations ou les tableaux de bord en production. Ce paramètre active ES|QL dans Discover. Si vous avez des commentaires sur cette expérience, contactez-nous via {link}", - "discover.context.contextOfTitle": "Les documents relatifs à #{anchorId}", - "discover.context.newerDocumentsWarning": "Seuls {docCount} documents plus récents que le document ancré ont été trouvés.", - "discover.context.olderDocumentsWarning": "Seuls {docCount} documents plus anciens que le document ancré ont été trouvés.", - "discover.context.pageTitle": "Les documents relatifs à #{anchorId}", - "discover.contextViewRoute.errorMessage": "Aucune donnée correspondante pour l'ID {dataViewId}", + "discover.advancedSettings.discover.showMultifieldsDescription": "Détermine si les {multiFields} doivent s'afficher dans la fenêtre de document étendue. Dans la plupart des cas, les champs multiples sont les mêmes que les champs d'origine. Cette option est uniquement disponible lorsque le paramètre ''searchFieldsFromSource'' est désactivé.", + "discover.context.contextOfTitle": "Documents relatifs à #{anchorId}", + "discover.context.newerDocumentsWarning": "Seuls {docCount} documents plus récents que le document ancré ont été trouvés.", + "discover.context.olderDocumentsWarning": "Seuls {docCount} documents plus anciens que le document ancré ont été trouvés.", + "discover.context.pageTitle": "Documents relatifs à #{anchorId}", + "discover.contextViewRoute.errorMessage": "Aucune vue de données correspondante pour l'ID {dataViewId}", "discover.doc.failedToLocateDataView": "Aucune vue de données ne correspond à l'ID {dataViewId}.", "discover.doc.pageTitle": "Document unique - #{id}", "discover.doc.somethingWentWrongDescription": "Index {indexName} manquant.", @@ -2282,7 +2281,6 @@ "discover.showingSavedDataViewWarningDescription": "Affichage de la vue de données enregistrée : \"{ownDataViewTitle}\" ({ownDataViewId})", "discover.singleDocRoute.errorMessage": "Aucune donnée correspondante pour l'ID {dataViewId}", "discover.textBasedMode.selectedColumnsCallout": "Affichage de {selectedColumnsNumber} champs sur {textBasedQueryColumnsNumber}. Ajoutez-en d’autres depuis la liste des champs disponibles.", - "discover.textBasedMode.techPreviewCalloutMessage": "ES|QL est actuellement en version d'évaluation technique. Pour en savoir plus, consultez {link}.", "discover.valueIsNotConfiguredDataViewIDWarningTitle": "{stateVal} n'est pas un ID de vue de données configuré", "discover.viewAlert.dataViewErrorText": "Échec de la vue des données de la règle d'alerte avec l'ID {alertId}.", "discover.advancedSettings.context.defaultSizeText": "Le nombre d'entrées connexes à afficher dans la vue contextuelle", @@ -2325,7 +2323,6 @@ "discover.advancedSettings.sortDefaultOrderTitle": "Sens de tri par défaut", "discover.advancedSettings.sortOrderAsc": "Croissant", "discover.advancedSettings.sortOrderDesc": "Décroissant", - "discover.advancedSettings.technicalPreviewLabel": "version d'évaluation technique", "discover.alerts.createSearchThreshold": "Créer une règle de seuil de recherche", "discover.alerts.manageRulesAndConnectors": "Gérer les règles et les connecteurs", "discover.alerts.missedTimeFieldToolTip": "La vue de données ne possède pas de champ temporel.", @@ -2499,7 +2496,6 @@ "discover.serverLocatorExtension.titleFromLocatorUnknown": "Recherche inconnue", "discover.singleDocRoute.errorTitle": "Une erreur s'est produite", "discover.skipToBottomButtonLabel": "Atteindre la fin du tableau", - "discover.textBasedMode.techPreviewCalloutLink": "documentation", "discover.topNav.managedContentLabel": "Cette recherche sauvegardée est gérée par Elastic. Les modifications effectuées ici doivent être enregistrées dans une nouvelle recherche sauvegardée.", "discover.topNav.openSearchPanel.manageSearchesButtonLabel": "Gérer les recherches", "discover.topNav.openSearchPanel.noSearchesFoundDescription": "Aucune recherche correspondante trouvée.", @@ -7046,7 +7042,6 @@ "unifiedSearch.query.queryBar.luceneLanguageName": "Lucene", "unifiedSearch.query.queryBar.searchInputPlaceholderForText": "Filtrer vos données", "unifiedSearch.query.queryBar.syntaxOptionsTitle": "Options de syntaxe", - "unifiedSearch.query.queryBar.textBasedLanguagesTechPreviewLabel": "Version d'évaluation technique", "unifiedSearch.query.queryBar.textBasedLanguagesTryLabel": "Essayer ES|QL", "unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "Tout le temps", "unifiedSearch.queryBarTopRow.submitButton.cancel": "Annuler", @@ -22986,7 +22981,6 @@ "xpack.lens.chartSwitch.noResults": "Résultats introuvables pour {term}.", "xpack.lens.config.createVisualizationLabel": "Créer la visualisation {lang}", "xpack.lens.config.editVisualizationLabel": "Modifier la visualisation {lang}", - "xpack.lens.config.experimentalLabelTextBased": "Version d'évaluation technique, {lang} propose actuellement des options de configuration limitées", "xpack.lens.configure.suggestedValuee": "Valeur suggérée : {value}", "xpack.lens.confirmModal.saveDuplicateButtonLabel": "Enregistrer {name}", "xpack.lens.confirmModal.saveDuplicateConfirmationMessage": "Il y a déjà une occurrence de {name} avec le titre \"{title}\". Voulez-vous tout de même enregistrer ?", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 09d1ecb7f11f3..0f8484d4ae0fc 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2243,7 +2243,6 @@ "discover.advancedSettings.disableDocumentExplorerDescription": "クラシックビューではなく、新しい{documentExplorerDocs}を使用するには、このオプションをオフにします。ドキュメントエクスプローラーでは、データの並べ替え、列のサイズ変更、全画面表示といった優れた機能を使用できます。", "discover.advancedSettings.discover.showFieldStatisticsDescription": "{fieldStatisticsDocs}を有効にすると、数値フィールドの最大/最小値やジオフィールドの地図といった詳細が表示されます。この機能はベータ段階で、変更される可能性があります。", "discover.advancedSettings.discover.showMultifieldsDescription": "拡張ドキュメントビューに{multiFields}が表示されるかどうかを制御します。ほとんどの場合、マルチフィールドは元のフィールドと同じです。「searchFieldsFromSource」がオフのときにのみこのオプションを使用できます。", - "discover.advancedSettings.enableESQLDescription": "{technicalPreviewLabel} このパッチプレビュー機能は実験段階です。本番の保存された検索、可視化、またはダッシュボードでは、この機能を信頼しないでください。この設定はDiscoverのES|QLを有効にします。このエクスペリエンスに関するフィードバックがございましたら、{link}からお問い合わせください", "discover.context.contextOfTitle": "#{anchorId}の周りのドキュメント", "discover.context.newerDocumentsWarning": "アンカーよりも新しいドキュメントは{docCount}件しか見つかりませんでした。", "discover.context.olderDocumentsWarning": "アンカーよりも古いドキュメントは{docCount}件しか見つかりませんでした。", @@ -2280,7 +2279,6 @@ "discover.showingSavedDataViewWarningDescription": "保存されたデータビューを表示しています:\"{ownDataViewTitle}\" ({ownDataViewId})", "discover.singleDocRoute.errorMessage": "ID {dataViewId}の一致するデータビューが見つかりません", "discover.textBasedMode.selectedColumnsCallout": "{textBasedQueryColumnsNumber}フィールド中{selectedColumnsNumber}フィールドを表示中です。利用可能なフィールドリストからさらに追加します。", - "discover.textBasedMode.techPreviewCalloutMessage": "ES|QLは現在テクニカルプレビューです。詳細については、{link}をご覧ください。", "discover.valueIsNotConfiguredDataViewIDWarningTitle": "{stateVal}は設定されたデータビューIDではありません", "discover.viewAlert.dataViewErrorText": "id {alertId}のアラートルールのデータビューのエラー。", "discover.advancedSettings.context.defaultSizeText": "コンテキストビューに表示される周りのエントリーの数", @@ -2323,7 +2321,6 @@ "discover.advancedSettings.sortDefaultOrderTitle": "デフォルトの並べ替え方向", "discover.advancedSettings.sortOrderAsc": "昇順", "discover.advancedSettings.sortOrderDesc": "降順", - "discover.advancedSettings.technicalPreviewLabel": "テクニカルプレビュー", "discover.alerts.createSearchThreshold": "検索しきい値ルールの作成", "discover.alerts.manageRulesAndConnectors": "ルールとコネクターの管理", "discover.alerts.missedTimeFieldToolTip": "データビューには時間フィールドがありません。", @@ -2497,7 +2494,6 @@ "discover.serverLocatorExtension.titleFromLocatorUnknown": "不明な検索", "discover.singleDocRoute.errorTitle": "エラーが発生しました", "discover.skipToBottomButtonLabel": "テーブルの最後に移動", - "discover.textBasedMode.techPreviewCalloutLink": "ドキュメンテーション", "discover.topNav.managedContentLabel": "この保存された検索は、Elasticによって管理されます。この変更は、新しく保存された検索に保存する必要があります。", "discover.topNav.openSearchPanel.manageSearchesButtonLabel": "検索の管理", "discover.topNav.openSearchPanel.noSearchesFoundDescription": "一致する検索が見つかりませんでした。", @@ -7035,7 +7031,6 @@ "unifiedSearch.query.queryBar.luceneLanguageName": "Lucene", "unifiedSearch.query.queryBar.searchInputPlaceholderForText": "データのフィルタリング", "unifiedSearch.query.queryBar.syntaxOptionsTitle": "構文オプション", - "unifiedSearch.query.queryBar.textBasedLanguagesTechPreviewLabel": "テクニカルプレビュー", "unifiedSearch.query.queryBar.textBasedLanguagesTryLabel": "ES|QLを試す", "unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "常時", "unifiedSearch.queryBarTopRow.submitButton.cancel": "キャンセル", @@ -22961,7 +22956,6 @@ "xpack.lens.chartSwitch.noResults": "{term}の結果が見つかりませんでした。", "xpack.lens.config.createVisualizationLabel": "{lang}ビジュアライゼーションを作成", "xpack.lens.config.editVisualizationLabel": "{lang}ビジュアライゼーションを編集", - "xpack.lens.config.experimentalLabelTextBased": "テクニカルプレビュー。現在、{lang}では、構成オプションは限られています。", "xpack.lens.configure.suggestedValuee": "候補の値:{value}", "xpack.lens.confirmModal.saveDuplicateButtonLabel": "{name} を保存", "xpack.lens.confirmModal.saveDuplicateConfirmationMessage": "「{title}」というタイトルの {name} がすでに存在します。保存しますか?", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index cbf15041b75d1..79ccce6fb30c0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2245,7 +2245,6 @@ "discover.advancedSettings.disableDocumentExplorerDescription": "要使用新的 {documentExplorerDocs},而非经典视图,请关闭此选项。Document Explorer 提供了更合理的数据排序、可调整大小的列和全屏视图。", "discover.advancedSettings.discover.showFieldStatisticsDescription": "启用 {fieldStatisticsDocs} 以显示详细信息,如数字字段的最小和最大值,或地理字段的地图。此功能为公测版,可能会进行更改。", "discover.advancedSettings.discover.showMultifieldsDescription": "控制 {multiFields} 是否显示在展开的文档视图中。多数情况下,多字段与原始字段相同。此选项仅在 `searchFieldsFromSource` 关闭时可用。", - "discover.advancedSettings.enableESQLDescription": "{technicalPreviewLabel} 此技术预览功能为高度实验性功能 -- 请勿在生产已保存搜索、可视化或仪表板中依赖此功能。此设置将在 Discover 中启用 ES|QL。如果具有与此体验有关的反馈,请通过 {link} 联系我们", "discover.context.contextOfTitle": "#{anchorId} 周围的文档", "discover.context.newerDocumentsWarning": "仅可以找到 {docCount} 个比定位标记新的文档。", "discover.context.olderDocumentsWarning": "仅可以找到 {docCount} 个比定位标记旧的文档。", @@ -2284,7 +2283,6 @@ "discover.showingSavedDataViewWarningDescription": "正在显示已保存数据视图:“{ownDataViewTitle}”({ownDataViewId})", "discover.singleDocRoute.errorMessage": "没有与 ID {dataViewId} 相匹配的数据视图", "discover.textBasedMode.selectedColumnsCallout": "正在显示 {selectedColumnsNumber} 个字段,共 {textBasedQueryColumnsNumber} 个。从可用字段列表中添加更多字段。", - "discover.textBasedMode.techPreviewCalloutMessage": "ES|QL 当前处于技术预览状态。有关更多信息,请参阅{link}。", "discover.valueIsNotConfiguredDataViewIDWarningTitle": "{stateVal} 不是配置的数据视图 ID", "discover.viewAlert.dataViewErrorText": "ID 为 {alertId} 的告警规则的数据视图失败。", "discover.advancedSettings.context.defaultSizeText": "要在上下文视图中显示的周围条目数目", @@ -2327,7 +2325,6 @@ "discover.advancedSettings.sortDefaultOrderTitle": "默认排序方向", "discover.advancedSettings.sortOrderAsc": "升序", "discover.advancedSettings.sortOrderDesc": "降序", - "discover.advancedSettings.technicalPreviewLabel": "技术预览", "discover.alerts.createSearchThreshold": "创建搜索阈值规则", "discover.alerts.manageRulesAndConnectors": "管理规则和连接器", "discover.alerts.missedTimeFieldToolTip": "数据视图没有时间字段。", @@ -2501,7 +2498,6 @@ "discover.serverLocatorExtension.titleFromLocatorUnknown": "未知搜索", "discover.singleDocRoute.errorTitle": "发生错误", "discover.skipToBottomButtonLabel": "转到表尾", - "discover.textBasedMode.techPreviewCalloutLink": "文档", "discover.topNav.managedContentLabel": "此已保存搜索由 Elastic 托管。此处的更改必须保存到新的已保存搜索。", "discover.topNav.openSearchPanel.manageSearchesButtonLabel": "管理搜索", "discover.topNav.openSearchPanel.noSearchesFoundDescription": "未找到匹配的搜索。", @@ -7049,7 +7045,6 @@ "unifiedSearch.query.queryBar.luceneLanguageName": "Lucene", "unifiedSearch.query.queryBar.searchInputPlaceholderForText": "筛选您的数据", "unifiedSearch.query.queryBar.syntaxOptionsTitle": "语法选项", - "unifiedSearch.query.queryBar.textBasedLanguagesTechPreviewLabel": "技术预览", "unifiedSearch.query.queryBar.textBasedLanguagesTryLabel": "尝试 ES|QL", "unifiedSearch.queryBarTopRow.datePicker.disabledLabel": "所有时间", "unifiedSearch.queryBarTopRow.submitButton.cancel": "取消", @@ -22993,7 +22988,6 @@ "xpack.lens.chartSwitch.noResults": "找不到 {term} 的结果。", "xpack.lens.config.createVisualizationLabel": "创建 {lang} 可视化", "xpack.lens.config.editVisualizationLabel": "编辑 {lang} 可视化", - "xpack.lens.config.experimentalLabelTextBased": "技术预览,{lang} 当前提供的配置选项数量有限", "xpack.lens.configure.suggestedValuee": "建议值:{value}", "xpack.lens.confirmModal.saveDuplicateButtonLabel": "保存“{name}”", "xpack.lens.confirmModal.saveDuplicateConfirmationMessage": "具有标题“{title}”的 {name} 已存在。是否确定要保存?", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/alerts_app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/alerts_app.tsx index 9bf267fb9852a..5b1ac69c0fa66 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/alerts_app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/alerts_app.tsx @@ -8,10 +8,8 @@ import React, { lazy } from 'react'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nProvider } from '@kbn/i18n-react'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; import { setDataViewsService } from '../common/lib/data_apis'; @@ -29,23 +27,18 @@ export const renderApp = (deps: TriggersAndActionsUiServices) => { }; export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => { - const { dataViews, theme, theme$ } = deps; - const isDarkMode = theme.getTheme().darkMode; + const { dataViews, i18n, theme } = deps; setDataViewsService(dataViews); return ( - - - - - - - - - - - - - + + + + + + + + + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index 808778e95a623..6116f4571990d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -8,12 +8,16 @@ import React, { lazy } from 'react'; import { Redirect } from 'react-router-dom'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; -import { ChromeBreadcrumb, CoreStart, CoreTheme, ScopedHistory } from '@kbn/core/public'; +import { + ChromeBreadcrumb, + CoreStart, + I18nStart, + ScopedHistory, + ThemeServiceStart, +} from '@kbn/core/public'; import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nProvider } from '@kbn/i18n-react'; -import { Observable } from 'rxjs'; import { KibanaFeature } from '@kbn/features-plugin/common'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; @@ -26,7 +30,6 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import { ruleDetailsRoute } from '@kbn/rule-data-utils'; import { QueryClientProvider } from '@tanstack/react-query'; @@ -74,7 +77,8 @@ export interface TriggersAndActionsUiServices extends CoreStart { history: ScopedHistory; kibanaFeatures: KibanaFeature[]; element: HTMLElement; - theme$: Observable; + i18n: I18nStart; + theme: ThemeServiceStart; unifiedSearch: UnifiedSearchPublicPluginStart; licensing: LicensingPluginStart; expressions: ExpressionsStart; @@ -92,26 +96,21 @@ export const renderApp = (deps: TriggersAndActionsUiServices) => { }; export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => { - const { dataViews, theme } = deps; + const { dataViews, i18n, theme } = deps; const sections: Section[] = ['rules', 'logs']; - const isDarkMode = theme.getTheme().darkMode; const sectionsRegex = sections.join('|'); setDataViewsService(dataViews); return ( - - - - - - - - - - - - - + + + + + + + + + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx index 8d23a724f2571..8fe0d9745d1c2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx @@ -10,10 +10,9 @@ import { Redirect } from 'react-router-dom'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { ChromeBreadcrumb, CoreStart, CoreTheme, ScopedHistory } from '@kbn/core/public'; import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nProvider } from '@kbn/i18n-react'; import { Observable } from 'rxjs'; import { KibanaFeature } from '@kbn/features-plugin/common'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; @@ -24,7 +23,6 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { QueryClientProvider } from '@tanstack/react-query'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import { DashboardStart } from '@kbn/dashboard-plugin/public'; import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; @@ -75,26 +73,21 @@ export const renderApp = (deps: TriggersAndActionsUiServices) => { }; export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => { - const { dataViews, theme } = deps; - const isDarkMode = theme.getTheme().darkMode; + const { dataViews, i18n, theme } = deps; const sections: Section[] = ['connectors', 'logs']; const sectionsRegex = sections.join('|'); setDataViewsService(dataViews); return ( - - - - - - - - - - - - - + + + + + + + + + ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_response.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_response.tsx index 8958cd484f1b9..b1d69c89094e9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_response.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_edit_response.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { useKibana } from '../../common/lib/kibana'; import { BulkEditResponse } from '../../types'; @@ -63,6 +63,8 @@ export interface UseBulkEditResponseProps { export function useBulkEditResponse(props: UseBulkEditResponseProps) { const { onSearchPopulate } = props; const { + i18n: i18nStart, + theme, notifications: { toasts }, } = useKibana().services; @@ -122,7 +124,7 @@ export function useBulkEditResponse(props: UseBulkEditResponseProps) { if (numberOfErrors === total) { toasts.addDanger({ title: failureMessage(numberOfErrors, translationMap[property]), - text: toMountPoint(renderToastErrorBody(response)), + text: toMountPoint(renderToastErrorBody(response), { i18n: i18nStart, theme }), }); return; } @@ -130,10 +132,10 @@ export function useBulkEditResponse(props: UseBulkEditResponseProps) { // Some failure toasts.addWarning({ title: someSuccessMessage(numberOfSuccess, numberOfErrors, translationMap[property]), - text: toMountPoint(renderToastErrorBody(response)), + text: toMountPoint(renderToastErrorBody(response), { i18n: i18nStart, theme }), }); }, - [toasts, renderToastErrorBody] + [i18nStart, theme, toasts, renderToastErrorBody] ); return useMemo(() => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_operation_toast.tsx b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_operation_toast.tsx index b71b0abaec7a3..88ad5c8f52958 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_operation_toast.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_bulk_operation_toast.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import type { BulkOperationError } from '@kbn/alerting-plugin/server'; import { useKibana } from '../../common/lib/kibana'; import { @@ -48,6 +48,8 @@ export const useBulkOperationToast = ({ onSearchPopulate?: (filter: string) => void; }) => { const { + i18n, + theme, notifications: { toasts }, } = useKibana().services; @@ -120,7 +122,7 @@ export const useBulkOperationToast = ({ SINGLE_RULE_TITLE, MULTIPLE_RULE_TITLE ), - text: toMountPoint(renderToastErrorBody(errors, 'danger')), + text: toMountPoint(renderToastErrorBody(errors, 'danger'), { i18n, theme }), }); return; } @@ -133,10 +135,10 @@ export const useBulkOperationToast = ({ SINGLE_RULE_TITLE, MULTIPLE_RULE_TITLE ), - text: toMountPoint(renderToastErrorBody(errors, 'warning')), + text: toMountPoint(renderToastErrorBody(errors, 'warning'), { i18n, theme }), }); }, - [toasts, renderToastErrorBody] + [i18n, theme, toasts, renderToastErrorBody] ); return useMemo(() => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx index e3bf983d7bd09..afb483b70f007 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx @@ -6,15 +6,17 @@ */ import * as React from 'react'; +import { coreMock } from '@kbn/core/public/mocks'; import { render, screen } from '@testing-library/react'; import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { ConnectorsSelection } from './connectors_selection'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ActionType, GenericValidationResult } from '../../../types'; import { EuiFieldText } from '@elastic/eui'; describe('connectors_selection', () => { + const core = coreMock.createStart(); const mockedActionParamsFields = React.lazy(async () => ({ default() { return ( @@ -92,7 +94,7 @@ describe('connectors_selection', () => { it('renders a selector', () => { const wrapper = mountWithIntl( - + { connectors={connectors} onConnectorSelected={jest.fn()} /> - +
); expect( @@ -111,7 +113,7 @@ describe('connectors_selection', () => { it('renders the title of the connector', () => { render( - + { connectors={connectors} onConnectorSelected={jest.fn()} /> - +
); expect(screen.getByRole('combobox')).toHaveValue('test pagerduty'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/modal.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/modal.test.tsx index f885610e33c6f..cb08e3f672fd8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/modal.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/inspect/modal.test.tsx @@ -6,8 +6,9 @@ */ import React from 'react'; +import { of } from 'rxjs'; import { fireEvent, render, screen } from '@testing-library/react'; -import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import type { ModalInspectProps } from './modal'; import { ModalInspectQuery } from './modal'; @@ -42,8 +43,11 @@ describe('Modal Inspect', () => { }; const renderModalInspectQuery = () => { + const theme = { theme$: of({ darkMode: false }) }; return render(, { - wrapper: ({ children }) => {children}, + wrapper: ({ children }) => ( + {children} + ), }); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx index 8bb60c0706f15..7421bec047e03 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_details.tsx @@ -24,7 +24,7 @@ import { EuiIconTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { RuleExecutionStatusErrorReasons, parseDuration } from '@kbn/alerting-plugin/common'; import { getRuleDetailsRoute } from '@kbn/rule-data-utils'; import { UpdateApiKeyModalConfirmation } from '../../../components/update_api_key_modal_confirmation'; @@ -90,12 +90,9 @@ const ruleDetailStyle = { export const RuleDetails: React.FunctionComponent = ({ rule, ruleType, - actionTypes, bulkDisableRules, bulkEnableRules, bulkDeleteRules, - snoozeRule, - unsnoozeRule, requestRefresh, refreshToken, }) => { @@ -107,6 +104,8 @@ export const RuleDetails: React.FunctionComponent = ({ setBreadcrumbs, chrome, http, + i18n: i18nStart, + theme, notifications: { toasts }, } = useKibana().services; const ruleReducer = useMemo(() => getRuleReducer(actionTypeRegistry), [actionTypeRegistry]); @@ -218,12 +217,20 @@ export const RuleDetails: React.FunctionComponent = ({ )} - + , + { i18n: i18nStart, theme } ), }); } } - }, [rule.schedule.interval, config.minimumScheduleInterval, toasts, hasEditButton]); + }, [ + i18nStart, + theme, + rule.schedule.interval, + config.minimumScheduleInterval, + toasts, + hasEditButton, + ]); const setRule = async () => { history.push(getRuleDetailsRoute(rule.id)); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx index 1b12fbffa4a1b..ef8057c0144d1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx @@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiTitle, EuiFlyoutHeader, EuiFlyout, EuiFlyoutBody, EuiPortal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common'; import { Rule, @@ -125,6 +125,8 @@ const RuleAdd = < http, notifications: { toasts }, application: { capabilities }, + i18n: i18nStart, + theme, } = useKibana().services; const canShowActions = hasShowActionsCapability(capabilities); @@ -270,7 +272,8 @@ const RuleAdd = < title: message.summary, ...(message.details && { text: toMountPoint( - {message.details} + {message.details}, + { i18n: i18nStart, theme } ), }), }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx index e4fcd86eef445..bfad87299a523 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx @@ -26,7 +26,7 @@ import { } from '@elastic/eui'; import { cloneDeep, omit } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common'; import { Rule, @@ -138,6 +138,8 @@ export const RuleEdit = < const { http, notifications: { toasts }, + i18n: i18nStart, + theme, } = useKibana().services; const setRule = (value: Rule) => { @@ -223,7 +225,8 @@ export const RuleEdit = < title: message.summary, ...(message.details && { text: toMountPoint( - {message.details} + {message.details}, + { i18n: i18nStart, theme } ), }), }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx index 1470fe2606107..624e6e5f276e7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx @@ -9,7 +9,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; import type { RuleSnooze } from '@kbn/alerting-plugin/common'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common'; import { EuiLoadingSpinner, @@ -65,6 +65,8 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ const { notifications: { toasts }, + i18n: i18nStart, + theme, } = useKibana().services; useEffect(() => { @@ -92,12 +94,13 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ title: message.summary, ...(message.details && { text: toMountPoint( - {message.details} + {message.details}, + { i18n: i18nStart, theme } ), }), }); throw new Error(); - }, [enableRule, toasts]); + }, [i18nStart, theme, enableRule, toasts]); const onEnable = useCallback(async () => { setIsUpdating(true); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index 92c2239f349a4..6fcce17f00542 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { capitalize, isEmpty, isEqual, sortBy } from 'lodash'; import { KueryNode } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; -import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { toMountPoint } from '@kbn/react-kibana-mount'; import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common'; import { RuleTypeModal } from '@kbn/alerts-ui-shared'; import React, { @@ -190,6 +190,8 @@ export const RulesList = ({ kibanaFeatures, notifications: { toasts }, ruleTypeRegistry, + i18n: i18nStart, + theme, } = kibanaServices; const canExecuteActions = hasExecuteActionsCapability(capabilities); @@ -692,7 +694,8 @@ export const RulesList = ({ toasts.addDanger({ title: parsedError.summary, text: toMountPoint( - {parsedError.details} + {parsedError.details}, + { theme, i18n: i18nStart } ), }); } else { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/test_utils.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/test_utils.tsx index bb55f89f15500..e6fab7c1e1edd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/test_utils.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/test_utils.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { QueryClient, QueryClientProvider, QueryClientProviderProps } from '@tanstack/react-query'; -import { of } from 'rxjs'; +import { coreMock } from '@kbn/core/public/mocks'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react'; -import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { TriggersAndActionsUiServices } from '../..'; import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock'; @@ -31,7 +31,7 @@ export const createAppMockRenderer = ( queryClientContext?: QueryClientProviderProps['context'] ): AppMockRenderer => { const services = createStartServicesMock(); - const theme$ = of({ darkMode: false }); + const core = coreMock.createStart(); const queryClient = new QueryClient({ defaultOptions: { @@ -53,7 +53,7 @@ export const createAppMockRenderer = ( const AppWrapper: React.FC<{ children: React.ReactElement }> = React.memo(({ children }) => ( - + {children} diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 174d3816792bf..fa0db949e3b50 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -313,7 +313,7 @@ export class Plugin unifiedSearch: pluginsStart.unifiedSearch, isCloud: Boolean(plugins.cloud?.isCloudEnabled), element: params.element, - theme$: params.theme$, + theme: params.theme, storage: new Storage(window.localStorage), setBreadcrumbs: params.setBreadcrumbs, history: params.history, @@ -411,7 +411,7 @@ export class Plugin unifiedSearch: pluginsStart.unifiedSearch, isCloud: Boolean(plugins.cloud?.isCloudEnabled), element: params.element, - theme$: params.theme$, + theme: params.theme, storage: new Storage(window.localStorage), setBreadcrumbs: params.setBreadcrumbs, history: params.history, diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json index c25e1697986bd..794cfbd6071d3 100644 --- a/x-pack/plugins/triggers_actions_ui/tsconfig.json +++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json @@ -61,7 +61,10 @@ "@kbn/code-editor", "@kbn/code-editor-mock", "@kbn/io-ts-utils", - "@kbn/lens-plugin" + "@kbn/lens-plugin", + "@kbn/react-kibana-context-render", + "@kbn/react-kibana-mount", + "@kbn/react-kibana-context-theme" ], "exclude": ["target/**/*"] } diff --git a/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts b/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts index ee13a801ed84f..b7180fcefdc31 100644 --- a/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts +++ b/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts @@ -190,7 +190,7 @@ export default function ({ getService }: FtrProviderContext) { 'should display the stats bar with the total number of models' ); // +1 because of the built-in model - await ml.trainedModels.assertStats(37); + await ml.trainedModels.assertStats(38); await ml.testExecution.logTestStep('should display the table'); await ml.trainedModels.assertTableExists(); diff --git a/x-pack/test/functional/es_archives/security_solution/ecs_compliant/mappings.json b/x-pack/test/functional/es_archives/security_solution/ecs_compliant/mappings.json index 1e2c79a11e26e..7a0dd1798cf1d 100644 --- a/x-pack/test/functional/es_archives/security_solution/ecs_compliant/mappings.json +++ b/x-pack/test/functional/es_archives/security_solution/ecs_compliant/mappings.json @@ -67,6 +67,9 @@ "name": { "type": "keyword" }, + "ip": { + "type": "ip" + }, "uptime": { "type": "long" } diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index bd4cd417c8af3..71005aa57b4e4 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -66,6 +66,11 @@ export const SUPPORTED_TRAINED_MODELS = { description: 'Tiny/Dummy PyTorch model (zero_shot)', modelTypes: ['pytorch', 'zero_shot'], }, + TINY_ELSER: { + name: 'pt_tiny_elser', + description: 'Tiny ELSER model', + modelTypes: ['pytorch'], + }, } as const; export type SupportedTrainedModelNamesType = typeof SUPPORTED_TRAINED_MODELS[keyof typeof SUPPORTED_TRAINED_MODELS]['name']; diff --git a/x-pack/test/functional/services/ml/resources/trained_model_definitions/pt_tiny_elser/config.json b/x-pack/test/functional/services/ml/resources/trained_model_definitions/pt_tiny_elser/config.json new file mode 100644 index 0000000000000..d8494c1d2067c --- /dev/null +++ b/x-pack/test/functional/services/ml/resources/trained_model_definitions/pt_tiny_elser/config.json @@ -0,0 +1,17 @@ +{ + "description": "Tiny ELSER model", + "model_type": "pytorch", + "inference_config": { + "text_expansion": { + "tokenization": { + "bert": { + "do_lower_case": true, + "with_special_tokens": true, + "max_sequence_length": 512, + "truncate": "first", + "span": -1 + } + } + } + } +} diff --git a/x-pack/test/functional/services/ml/resources/trained_model_definitions/pt_tiny_elser/traced_pytorch_model.pt b/x-pack/test/functional/services/ml/resources/trained_model_definitions/pt_tiny_elser/traced_pytorch_model.pt new file mode 100644 index 0000000000000..330407df301e9 Binary files /dev/null and b/x-pack/test/functional/services/ml/resources/trained_model_definitions/pt_tiny_elser/traced_pytorch_model.pt differ diff --git a/x-pack/test/functional/services/ml/resources/trained_model_definitions/pt_tiny_elser/vocabulary.json b/x-pack/test/functional/services/ml/resources/trained_model_definitions/pt_tiny_elser/vocabulary.json new file mode 100644 index 0000000000000..55d65ab4f26dc --- /dev/null +++ b/x-pack/test/functional/services/ml/resources/trained_model_definitions/pt_tiny_elser/vocabulary.json @@ -0,0 +1,30526 @@ +{ + "vocabulary": [ + "[PAD]", + "[unused0]", + "[unused1]", + "[unused2]", + "[unused3]", + "[unused4]", + "[unused5]", + "[unused6]", + "[unused7]", + "[unused8]", + "[unused9]", + "[unused10]", + "[unused11]", + "[unused12]", + "[unused13]", + "[unused14]", + "[unused15]", + "[unused16]", + "[unused17]", + "[unused18]", + "[unused19]", + "[unused20]", + "[unused21]", + "[unused22]", + "[unused23]", + "[unused24]", + "[unused25]", + "[unused26]", + "[unused27]", + "[unused28]", + "[unused29]", + "[unused30]", + "[unused31]", + "[unused32]", + "[unused33]", + "[unused34]", + "[unused35]", + "[unused36]", + "[unused37]", + "[unused38]", + "[unused39]", + "[unused40]", + "[unused41]", + "[unused42]", + "[unused43]", + "[unused44]", + "[unused45]", + "[unused46]", + "[unused47]", + "[unused48]", + "[unused49]", + "[unused50]", + "[unused51]", + "[unused52]", + "[unused53]", + "[unused54]", + "[unused55]", + "[unused56]", + "[unused57]", + "[unused58]", + "[unused59]", + "[unused60]", + "[unused61]", + "[unused62]", + "[unused63]", + "[unused64]", + "[unused65]", + "[unused66]", + "[unused67]", + "[unused68]", + "[unused69]", + "[unused70]", + "[unused71]", + "[unused72]", + "[unused73]", + "[unused74]", + "[unused75]", + "[unused76]", + "[unused77]", + "[unused78]", + "[unused79]", + "[unused80]", + "[unused81]", + "[unused82]", + "[unused83]", + "[unused84]", + "[unused85]", + "[unused86]", + "[unused87]", + "[unused88]", + "[unused89]", + "[unused90]", + "[unused91]", + "[unused92]", + "[unused93]", + "[unused94]", + "[unused95]", + "[unused96]", + "[unused97]", + "[unused98]", + "[UNK]", + "[CLS]", + "[SEP]", + "[MASK]", + "[unused99]", + "[unused100]", + "[unused101]", + "[unused102]", + "[unused103]", + "[unused104]", + "[unused105]", + "[unused106]", + "[unused107]", + "[unused108]", + "[unused109]", + "[unused110]", + "[unused111]", + "[unused112]", + "[unused113]", + "[unused114]", + "[unused115]", + "[unused116]", + "[unused117]", + "[unused118]", + "[unused119]", + "[unused120]", + "[unused121]", + "[unused122]", + "[unused123]", + "[unused124]", + "[unused125]", + "[unused126]", + "[unused127]", + "[unused128]", + "[unused129]", + "[unused130]", + "[unused131]", + "[unused132]", + "[unused133]", + "[unused134]", + "[unused135]", + "[unused136]", + "[unused137]", + "[unused138]", + "[unused139]", + "[unused140]", + "[unused141]", + "[unused142]", + "[unused143]", + "[unused144]", + "[unused145]", + "[unused146]", + "[unused147]", + "[unused148]", + "[unused149]", + "[unused150]", + "[unused151]", + "[unused152]", + "[unused153]", + "[unused154]", + "[unused155]", + "[unused156]", + "[unused157]", + "[unused158]", + "[unused159]", + "[unused160]", + "[unused161]", + "[unused162]", + "[unused163]", + "[unused164]", + "[unused165]", + "[unused166]", + "[unused167]", + "[unused168]", + "[unused169]", + "[unused170]", + "[unused171]", + "[unused172]", + "[unused173]", + "[unused174]", + "[unused175]", + "[unused176]", + "[unused177]", + "[unused178]", + "[unused179]", + "[unused180]", + "[unused181]", + "[unused182]", + "[unused183]", + "[unused184]", + "[unused185]", + "[unused186]", + "[unused187]", + "[unused188]", + "[unused189]", + "[unused190]", + "[unused191]", + "[unused192]", + "[unused193]", + "[unused194]", + "[unused195]", + "[unused196]", + "[unused197]", + "[unused198]", + "[unused199]", + "[unused200]", + "[unused201]", + "[unused202]", + "[unused203]", + "[unused204]", + "[unused205]", + "[unused206]", + "[unused207]", + "[unused208]", + "[unused209]", + "[unused210]", + "[unused211]", + "[unused212]", + "[unused213]", + "[unused214]", + "[unused215]", + "[unused216]", + "[unused217]", + "[unused218]", + "[unused219]", + "[unused220]", + "[unused221]", + "[unused222]", + "[unused223]", + "[unused224]", + "[unused225]", + "[unused226]", + "[unused227]", + "[unused228]", + "[unused229]", + "[unused230]", + "[unused231]", + "[unused232]", + "[unused233]", + "[unused234]", + "[unused235]", + "[unused236]", + "[unused237]", + "[unused238]", + "[unused239]", + "[unused240]", + "[unused241]", + "[unused242]", + "[unused243]", + "[unused244]", + "[unused245]", + "[unused246]", + "[unused247]", + "[unused248]", + "[unused249]", + "[unused250]", + "[unused251]", + "[unused252]", + "[unused253]", + "[unused254]", + "[unused255]", + "[unused256]", + "[unused257]", + "[unused258]", + "[unused259]", + "[unused260]", + "[unused261]", + "[unused262]", + "[unused263]", + "[unused264]", + "[unused265]", + "[unused266]", + "[unused267]", + "[unused268]", + "[unused269]", + "[unused270]", + "[unused271]", + "[unused272]", + "[unused273]", + "[unused274]", + "[unused275]", + "[unused276]", + "[unused277]", + "[unused278]", + "[unused279]", + "[unused280]", + "[unused281]", + "[unused282]", + "[unused283]", + "[unused284]", + "[unused285]", + "[unused286]", + "[unused287]", + "[unused288]", + "[unused289]", + "[unused290]", + "[unused291]", + "[unused292]", + "[unused293]", + "[unused294]", + "[unused295]", + "[unused296]", + "[unused297]", + "[unused298]", + "[unused299]", + "[unused300]", + "[unused301]", + "[unused302]", + "[unused303]", + "[unused304]", + "[unused305]", + "[unused306]", + "[unused307]", + "[unused308]", + "[unused309]", + "[unused310]", + "[unused311]", + "[unused312]", + "[unused313]", + "[unused314]", + "[unused315]", + "[unused316]", + "[unused317]", + "[unused318]", + "[unused319]", + "[unused320]", + "[unused321]", + "[unused322]", + "[unused323]", + "[unused324]", + "[unused325]", + "[unused326]", + "[unused327]", + "[unused328]", + "[unused329]", + "[unused330]", + "[unused331]", + "[unused332]", + "[unused333]", + "[unused334]", + "[unused335]", + "[unused336]", + "[unused337]", + "[unused338]", + "[unused339]", + "[unused340]", + "[unused341]", + "[unused342]", + "[unused343]", + "[unused344]", + "[unused345]", + "[unused346]", + "[unused347]", + "[unused348]", + "[unused349]", + "[unused350]", + "[unused351]", + "[unused352]", + "[unused353]", + "[unused354]", + "[unused355]", + "[unused356]", + "[unused357]", + "[unused358]", + "[unused359]", + "[unused360]", + "[unused361]", + "[unused362]", + "[unused363]", + "[unused364]", + "[unused365]", + "[unused366]", + "[unused367]", + "[unused368]", + "[unused369]", + "[unused370]", + "[unused371]", + "[unused372]", + "[unused373]", + "[unused374]", + "[unused375]", + "[unused376]", + "[unused377]", + "[unused378]", + "[unused379]", + "[unused380]", + "[unused381]", + "[unused382]", + "[unused383]", + "[unused384]", + "[unused385]", + "[unused386]", + "[unused387]", + "[unused388]", + "[unused389]", + "[unused390]", + "[unused391]", + "[unused392]", + "[unused393]", + "[unused394]", + "[unused395]", + "[unused396]", + "[unused397]", + "[unused398]", + "[unused399]", + "[unused400]", + "[unused401]", + "[unused402]", + "[unused403]", + "[unused404]", + "[unused405]", + "[unused406]", + "[unused407]", + "[unused408]", + "[unused409]", + "[unused410]", + "[unused411]", + "[unused412]", + "[unused413]", + "[unused414]", + "[unused415]", + "[unused416]", + "[unused417]", + "[unused418]", + "[unused419]", + "[unused420]", + "[unused421]", + "[unused422]", + "[unused423]", + "[unused424]", + "[unused425]", + "[unused426]", + "[unused427]", + "[unused428]", + "[unused429]", + "[unused430]", + "[unused431]", + "[unused432]", + "[unused433]", + "[unused434]", + "[unused435]", + "[unused436]", + "[unused437]", + "[unused438]", + "[unused439]", + "[unused440]", + "[unused441]", + "[unused442]", + "[unused443]", + "[unused444]", + "[unused445]", + "[unused446]", + "[unused447]", + "[unused448]", + "[unused449]", + "[unused450]", + "[unused451]", + "[unused452]", + "[unused453]", + "[unused454]", + "[unused455]", + "[unused456]", + "[unused457]", + "[unused458]", + "[unused459]", + "[unused460]", + "[unused461]", + "[unused462]", + "[unused463]", + "[unused464]", + "[unused465]", + "[unused466]", + "[unused467]", + "[unused468]", + "[unused469]", + "[unused470]", + "[unused471]", + "[unused472]", + "[unused473]", + "[unused474]", + "[unused475]", + "[unused476]", + "[unused477]", + "[unused478]", + "[unused479]", + "[unused480]", + "[unused481]", + "[unused482]", + "[unused483]", + "[unused484]", + "[unused485]", + "[unused486]", + "[unused487]", + "[unused488]", + "[unused489]", + "[unused490]", + "[unused491]", + "[unused492]", + "[unused493]", + "[unused494]", + "[unused495]", + "[unused496]", + "[unused497]", + "[unused498]", + "[unused499]", + "[unused500]", + "[unused501]", + "[unused502]", + "[unused503]", + "[unused504]", + "[unused505]", + "[unused506]", + "[unused507]", + "[unused508]", + "[unused509]", + "[unused510]", + "[unused511]", + "[unused512]", + "[unused513]", + "[unused514]", + "[unused515]", + "[unused516]", + "[unused517]", + "[unused518]", + "[unused519]", + "[unused520]", + "[unused521]", + "[unused522]", + "[unused523]", + "[unused524]", + "[unused525]", + "[unused526]", + "[unused527]", + "[unused528]", + "[unused529]", + "[unused530]", + "[unused531]", + "[unused532]", + "[unused533]", + "[unused534]", + "[unused535]", + "[unused536]", + "[unused537]", + "[unused538]", + "[unused539]", + "[unused540]", + "[unused541]", + "[unused542]", + "[unused543]", + "[unused544]", + "[unused545]", + "[unused546]", + "[unused547]", + "[unused548]", + "[unused549]", + "[unused550]", + "[unused551]", + "[unused552]", + "[unused553]", + "[unused554]", + "[unused555]", + "[unused556]", + "[unused557]", + "[unused558]", + "[unused559]", + "[unused560]", + "[unused561]", + "[unused562]", + "[unused563]", + "[unused564]", + "[unused565]", + "[unused566]", + "[unused567]", + "[unused568]", + "[unused569]", + "[unused570]", + "[unused571]", + "[unused572]", + "[unused573]", + "[unused574]", + "[unused575]", + "[unused576]", + "[unused577]", + "[unused578]", + "[unused579]", + "[unused580]", + "[unused581]", + "[unused582]", + "[unused583]", + "[unused584]", + "[unused585]", + "[unused586]", + "[unused587]", + "[unused588]", + "[unused589]", + "[unused590]", + "[unused591]", + "[unused592]", + "[unused593]", + "[unused594]", + "[unused595]", + "[unused596]", + "[unused597]", + "[unused598]", + "[unused599]", + "[unused600]", + "[unused601]", + "[unused602]", + "[unused603]", + "[unused604]", + "[unused605]", + "[unused606]", + "[unused607]", + "[unused608]", + "[unused609]", + "[unused610]", + "[unused611]", + "[unused612]", + "[unused613]", + "[unused614]", + "[unused615]", + "[unused616]", + "[unused617]", + "[unused618]", + "[unused619]", + "[unused620]", + "[unused621]", + "[unused622]", + "[unused623]", + "[unused624]", + "[unused625]", + "[unused626]", + "[unused627]", + "[unused628]", + "[unused629]", + "[unused630]", + "[unused631]", + "[unused632]", + "[unused633]", + "[unused634]", + "[unused635]", + "[unused636]", + "[unused637]", + "[unused638]", + "[unused639]", + "[unused640]", + "[unused641]", + "[unused642]", + "[unused643]", + "[unused644]", + "[unused645]", + "[unused646]", + "[unused647]", + "[unused648]", + "[unused649]", + "[unused650]", + "[unused651]", + "[unused652]", + "[unused653]", + "[unused654]", + "[unused655]", + "[unused656]", + "[unused657]", + "[unused658]", + "[unused659]", + "[unused660]", + "[unused661]", + "[unused662]", + "[unused663]", + "[unused664]", + "[unused665]", + "[unused666]", + "[unused667]", + "[unused668]", + "[unused669]", + "[unused670]", + "[unused671]", + "[unused672]", + "[unused673]", + "[unused674]", + "[unused675]", + "[unused676]", + "[unused677]", + "[unused678]", + "[unused679]", + "[unused680]", + "[unused681]", + "[unused682]", + "[unused683]", + "[unused684]", + "[unused685]", + "[unused686]", + "[unused687]", + "[unused688]", + "[unused689]", + "[unused690]", + "[unused691]", + "[unused692]", + "[unused693]", + "[unused694]", + "[unused695]", + "[unused696]", + "[unused697]", + "[unused698]", + "[unused699]", + "[unused700]", + "[unused701]", + "[unused702]", + "[unused703]", + "[unused704]", + "[unused705]", + "[unused706]", + "[unused707]", + "[unused708]", + "[unused709]", + "[unused710]", + "[unused711]", + "[unused712]", + "[unused713]", + "[unused714]", + "[unused715]", + "[unused716]", + "[unused717]", + "[unused718]", + "[unused719]", + "[unused720]", + "[unused721]", + "[unused722]", + "[unused723]", + "[unused724]", + "[unused725]", + "[unused726]", + "[unused727]", + "[unused728]", + "[unused729]", + "[unused730]", + "[unused731]", + "[unused732]", + "[unused733]", + "[unused734]", + "[unused735]", + "[unused736]", + "[unused737]", + "[unused738]", + "[unused739]", + "[unused740]", + "[unused741]", + "[unused742]", + "[unused743]", + "[unused744]", + "[unused745]", + "[unused746]", + "[unused747]", + "[unused748]", + "[unused749]", + "[unused750]", + "[unused751]", + "[unused752]", + "[unused753]", + "[unused754]", + "[unused755]", + "[unused756]", + "[unused757]", + "[unused758]", + "[unused759]", + "[unused760]", + "[unused761]", + "[unused762]", + "[unused763]", + "[unused764]", + "[unused765]", + "[unused766]", + "[unused767]", + "[unused768]", + "[unused769]", + "[unused770]", + "[unused771]", + "[unused772]", + "[unused773]", + "[unused774]", + "[unused775]", + "[unused776]", + "[unused777]", + "[unused778]", + "[unused779]", + "[unused780]", + "[unused781]", + "[unused782]", + "[unused783]", + "[unused784]", + "[unused785]", + "[unused786]", + "[unused787]", + "[unused788]", + "[unused789]", + "[unused790]", + "[unused791]", + "[unused792]", + "[unused793]", + "[unused794]", + "[unused795]", + "[unused796]", + "[unused797]", + "[unused798]", + "[unused799]", + "[unused800]", + "[unused801]", + "[unused802]", + "[unused803]", + "[unused804]", + "[unused805]", + "[unused806]", + "[unused807]", + "[unused808]", + "[unused809]", + "[unused810]", + "[unused811]", + "[unused812]", + "[unused813]", + "[unused814]", + "[unused815]", + "[unused816]", + "[unused817]", + "[unused818]", + "[unused819]", + "[unused820]", + "[unused821]", + "[unused822]", + "[unused823]", + "[unused824]", + "[unused825]", + "[unused826]", + "[unused827]", + "[unused828]", + "[unused829]", + "[unused830]", + "[unused831]", + "[unused832]", + "[unused833]", + "[unused834]", + "[unused835]", + "[unused836]", + "[unused837]", + "[unused838]", + "[unused839]", + "[unused840]", + "[unused841]", + "[unused842]", + "[unused843]", + "[unused844]", + "[unused845]", + "[unused846]", + "[unused847]", + "[unused848]", + "[unused849]", + "[unused850]", + "[unused851]", + "[unused852]", + "[unused853]", + "[unused854]", + "[unused855]", + "[unused856]", + "[unused857]", + "[unused858]", + "[unused859]", + "[unused860]", + "[unused861]", + "[unused862]", + "[unused863]", + "[unused864]", + "[unused865]", + "[unused866]", + "[unused867]", + "[unused868]", + "[unused869]", + "[unused870]", + "[unused871]", + "[unused872]", + "[unused873]", + "[unused874]", + "[unused875]", + "[unused876]", + "[unused877]", + "[unused878]", + "[unused879]", + "[unused880]", + "[unused881]", + "[unused882]", + "[unused883]", + "[unused884]", + "[unused885]", + "[unused886]", + "[unused887]", + "[unused888]", + "[unused889]", + "[unused890]", + "[unused891]", + "[unused892]", + "[unused893]", + "[unused894]", + "[unused895]", + "[unused896]", + "[unused897]", + "[unused898]", + "[unused899]", + "[unused900]", + "[unused901]", + "[unused902]", + "[unused903]", + "[unused904]", + "[unused905]", + "[unused906]", + "[unused907]", + "[unused908]", + "[unused909]", + "[unused910]", + "[unused911]", + "[unused912]", + "[unused913]", + "[unused914]", + "[unused915]", + "[unused916]", + "[unused917]", + "[unused918]", + "[unused919]", + "[unused920]", + "[unused921]", + "[unused922]", + "[unused923]", + "[unused924]", + "[unused925]", + "[unused926]", + "[unused927]", + "[unused928]", + "[unused929]", + "[unused930]", + "[unused931]", + "[unused932]", + "[unused933]", + "[unused934]", + "[unused935]", + "[unused936]", + "[unused937]", + "[unused938]", + "[unused939]", + "[unused940]", + "[unused941]", + "[unused942]", + "[unused943]", + "[unused944]", + "[unused945]", + "[unused946]", + "[unused947]", + "[unused948]", + "[unused949]", + "[unused950]", + "[unused951]", + "[unused952]", + "[unused953]", + "[unused954]", + "[unused955]", + "[unused956]", + "[unused957]", + "[unused958]", + "[unused959]", + "[unused960]", + "[unused961]", + "[unused962]", + "[unused963]", + "[unused964]", + "[unused965]", + "[unused966]", + "[unused967]", + "[unused968]", + "[unused969]", + "[unused970]", + "[unused971]", + "[unused972]", + "[unused973]", + "[unused974]", + "[unused975]", + "[unused976]", + "[unused977]", + "[unused978]", + "[unused979]", + "[unused980]", + "[unused981]", + "[unused982]", + "[unused983]", + "[unused984]", + "[unused985]", + "[unused986]", + "[unused987]", + "[unused988]", + "[unused989]", + "[unused990]", + "[unused991]", + "[unused992]", + "[unused993]", + "!", + "\"", + "#", + "$", + "%", + "&", + "'", + "(", + ")", + "*", + "+", + ",", + "-", + ".", + "/", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + ":", + ";", + "<", + "=", + ">", + "?", + "@", + "[", + "\\", + "]", + "^", + "_", + "`", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "{", + "|", + "}", + "~", + "\u00a1", + "\u00a2", + "\u00a3", + "\u00a4", + "\u00a5", + "\u00a6", + "\u00a7", + "\u00a8", + "\u00a9", + "\u00aa", + "\u00ab", + "\u00ac", + "\u00ae", + "\u00b0", + "\u00b1", + "\u00b2", + "\u00b3", + "\u00b4", + "\u00b5", + "\u00b6", + "\u00b7", + "\u00b9", + "\u00ba", + "\u00bb", + "\u00bc", + "\u00bd", + "\u00be", + "\u00bf", + "\u00d7", + "\u00df", + "\u00e6", + "\u00f0", + "\u00f7", + "\u00f8", + "\u00fe", + "\u0111", + "\u0127", + "\u0131", + "\u0142", + "\u014b", + "\u0153", + "\u0192", + "\u0250", + "\u0251", + "\u0252", + "\u0254", + "\u0255", + "\u0259", + "\u025b", + "\u0261", + "\u0263", + "\u0268", + "\u026a", + "\u026b", + "\u026c", + "\u026f", + "\u0272", + "\u0274", + "\u0279", + "\u027e", + "\u0280", + "\u0281", + "\u0282", + "\u0283", + "\u0289", + "\u028a", + "\u028b", + "\u028c", + "\u028e", + "\u0290", + "\u0291", + "\u0292", + "\u0294", + "\u02b0", + "\u02b2", + "\u02b3", + "\u02b7", + "\u02b8", + "\u02bb", + "\u02bc", + "\u02be", + "\u02bf", + "\u02c8", + "\u02d0", + "\u02e1", + "\u02e2", + "\u02e3", + "\u02e4", + "\u03b1", + "\u03b2", + "\u03b3", + "\u03b4", + "\u03b5", + "\u03b6", + "\u03b7", + "\u03b8", + "\u03b9", + "\u03ba", + "\u03bb", + "\u03bc", + "\u03bd", + "\u03be", + "\u03bf", + "\u03c0", + "\u03c1", + "\u03c2", + "\u03c3", + "\u03c4", + "\u03c5", + "\u03c6", + "\u03c7", + "\u03c8", + "\u03c9", + "\u0430", + "\u0431", + "\u0432", + "\u0433", + "\u0434", + "\u0435", + "\u0436", + "\u0437", + "\u0438", + "\u043a", + "\u043b", + "\u043c", + "\u043d", + "\u043e", + "\u043f", + "\u0440", + "\u0441", + "\u0442", + "\u0443", + "\u0444", + "\u0445", + "\u0446", + "\u0447", + "\u0448", + "\u0449", + "\u044a", + "\u044b", + "\u044c", + "\u044d", + "\u044e", + "\u044f", + "\u0452", + "\u0454", + "\u0456", + "\u0458", + "\u0459", + "\u045a", + "\u045b", + "\u04cf", + "\u0561", + "\u0562", + "\u0563", + "\u0564", + "\u0565", + "\u0569", + "\u056b", + "\u056c", + "\u056f", + "\u0570", + "\u0574", + "\u0575", + "\u0576", + "\u0578", + "\u057a", + "\u057d", + "\u057e", + "\u057f", + "\u0580", + "\u0582", + "\u0584", + "\u05be", + "\u05d0", + "\u05d1", + "\u05d2", + "\u05d3", + "\u05d4", + "\u05d5", + "\u05d6", + "\u05d7", + "\u05d8", + "\u05d9", + "\u05da", + "\u05db", + "\u05dc", + "\u05dd", + "\u05de", + "\u05df", + "\u05e0", + "\u05e1", + "\u05e2", + "\u05e3", + "\u05e4", + "\u05e5", + "\u05e6", + "\u05e7", + "\u05e8", + "\u05e9", + "\u05ea", + "\u060c", + "\u0621", + "\u0627", + "\u0628", + "\u0629", + "\u062a", + "\u062b", + "\u062c", + "\u062d", + "\u062e", + "\u062f", + "\u0630", + "\u0631", + "\u0632", + "\u0633", + "\u0634", + "\u0635", + "\u0636", + "\u0637", + "\u0638", + "\u0639", + "\u063a", + "\u0640", + "\u0641", + "\u0642", + "\u0643", + "\u0644", + "\u0645", + "\u0646", + "\u0647", + "\u0648", + "\u0649", + "\u064a", + "\u0679", + "\u067e", + "\u0686", + "\u06a9", + "\u06af", + "\u06ba", + "\u06be", + "\u06c1", + "\u06cc", + "\u06d2", + "\u0905", + "\u0906", + "\u0909", + "\u090f", + "\u0915", + "\u0916", + "\u0917", + "\u091a", + "\u091c", + "\u091f", + "\u0921", + "\u0923", + "\u0924", + "\u0925", + "\u0926", + "\u0927", + "\u0928", + "\u092a", + "\u092c", + "\u092d", + "\u092e", + "\u092f", + "\u0930", + "\u0932", + "\u0935", + "\u0936", + "\u0937", + "\u0938", + "\u0939", + "\u093e", + "\u093f", + "\u0940", + "\u094b", + "\u0964", + "\u0965", + "\u0982", + "\u0985", + "\u0986", + "\u0987", + "\u0989", + "\u098f", + "\u0993", + "\u0995", + "\u0996", + "\u0997", + "\u099a", + "\u099b", + "\u099c", + "\u099f", + "\u09a1", + "\u09a3", + "\u09a4", + "\u09a5", + "\u09a6", + "\u09a7", + "\u09a8", + "\u09aa", + "\u09ac", + "\u09ad", + "\u09ae", + "\u09af", + "\u09b0", + "\u09b2", + "\u09b6", + "\u09b7", + "\u09b8", + "\u09b9", + "\u09be", + "\u09bf", + "\u09c0", + "\u09c7", + "\u0b95", + "\u0b9a", + "\u0b9f", + "\u0ba4", + "\u0ba8", + "\u0ba9", + "\u0baa", + "\u0bae", + "\u0baf", + "\u0bb0", + "\u0bb2", + "\u0bb3", + "\u0bb5", + "\u0bbe", + "\u0bbf", + "\u0bc1", + "\u0bc7", + "\u0bc8", + "\u0ca8", + "\u0cb0", + "\u0cbe", + "\u0d9a", + "\u0dba", + "\u0dbb", + "\u0dbd", + "\u0dc0", + "\u0dcf", + "\u0e01", + "\u0e07", + "\u0e15", + "\u0e17", + "\u0e19", + "\u0e1e", + "\u0e21", + "\u0e22", + "\u0e23", + "\u0e25", + "\u0e27", + "\u0e2a", + "\u0e2d", + "\u0e32", + "\u0e40", + "\u0f0b", + "\u0f0d", + "\u0f42", + "\u0f44", + "\u0f51", + "\u0f53", + "\u0f54", + "\u0f56", + "\u0f58", + "\u0f60", + "\u0f62", + "\u0f63", + "\u0f66", + "\u1019", + "\u10d0", + "\u10d1", + "\u10d2", + "\u10d3", + "\u10d4", + "\u10d5", + "\u10d7", + "\u10d8", + "\u10d9", + "\u10da", + "\u10db", + "\u10dc", + "\u10dd", + "\u10e0", + "\u10e1", + "\u10e2", + "\u10e3", + "\u1100", + "\u1102", + "\u1103", + "\u1105", + "\u1106", + "\u1107", + "\u1109", + "\u110a", + "\u110b", + "\u110c", + "\u110e", + "\u110f", + "\u1110", + "\u1111", + "\u1112", + "\u1161", + "\u1162", + "\u1165", + "\u1166", + "\u1167", + "\u1169", + "\u116a", + "\u116d", + "\u116e", + "\u116f", + "\u1172", + "\u1173", + "\u1174", + "\u1175", + "\u11a8", + "\u11ab", + "\u11af", + "\u11b7", + "\u11b8", + "\u11bc", + "\u1d2c", + "\u1d2e", + "\u1d30", + "\u1d35", + "\u1d3a", + "\u1d40", + "\u1d43", + "\u1d47", + "\u1d48", + "\u1d49", + "\u1d4d", + "\u1d4f", + "\u1d50", + "\u1d52", + "\u1d56", + "\u1d57", + "\u1d58", + "\u1d62", + "\u1d63", + "\u1d64", + "\u1d65", + "\u1d9c", + "\u1da0", + "\u2010", + "\u2011", + "\u2012", + "\u2013", + "\u2014", + "\u2015", + "\u2016", + "\u2018", + "\u2019", + "\u201a", + "\u201c", + "\u201d", + "\u201e", + "\u2020", + "\u2021", + "\u2022", + "\u2026", + "\u2030", + "\u2032", + "\u2033", + "\u203a", + "\u203f", + "\u2044", + "\u2070", + "\u2071", + "\u2074", + "\u2075", + "\u2076", + "\u2077", + "\u2078", + "\u2079", + "\u207a", + "\u207b", + "\u207f", + "\u2080", + "\u2081", + "\u2082", + "\u2083", + "\u2084", + "\u2085", + "\u2086", + "\u2087", + "\u2088", + "\u2089", + "\u208a", + "\u208d", + "\u208e", + "\u2090", + "\u2091", + "\u2092", + "\u2093", + "\u2095", + "\u2096", + "\u2097", + "\u2098", + "\u2099", + "\u209a", + "\u209b", + "\u209c", + "\u20a4", + "\u20a9", + "\u20ac", + "\u20b1", + "\u20b9", + "\u2113", + "\u2116", + "\u211d", + "\u2122", + "\u2153", + "\u2154", + "\u2190", + "\u2191", + "\u2192", + "\u2193", + "\u2194", + "\u21a6", + "\u21c4", + "\u21cc", + "\u21d2", + "\u2202", + "\u2205", + "\u2206", + "\u2207", + "\u2208", + "\u2212", + "\u2217", + "\u2218", + "\u221a", + "\u221e", + "\u2227", + "\u2228", + "\u2229", + "\u222a", + "\u2248", + "\u2261", + "\u2264", + "\u2265", + "\u2282", + "\u2286", + "\u2295", + "\u2297", + "\u22c5", + "\u2500", + "\u2502", + "\u25a0", + "\u25aa", + "\u25cf", + "\u2605", + "\u2606", + "\u2609", + "\u2660", + "\u2663", + "\u2665", + "\u2666", + "\u266d", + "\u266f", + "\u27e8", + "\u27e9", + "\u2c7c", + "\u2ea9", + "\u2ebc", + "\u2f65", + "\u3001", + "\u3002", + "\u3008", + "\u3009", + "\u300a", + "\u300b", + "\u300c", + "\u300d", + "\u300e", + "\u300f", + "\u301c", + "\u3042", + "\u3044", + "\u3046", + "\u3048", + "\u304a", + "\u304b", + "\u304d", + "\u304f", + "\u3051", + "\u3053", + "\u3055", + "\u3057", + "\u3059", + "\u305b", + "\u305d", + "\u305f", + "\u3061", + "\u3063", + "\u3064", + "\u3066", + "\u3068", + "\u306a", + "\u306b", + "\u306c", + "\u306d", + "\u306e", + "\u306f", + "\u3072", + "\u3075", + "\u3078", + "\u307b", + "\u307e", + "\u307f", + "\u3080", + "\u3081", + "\u3082", + "\u3084", + "\u3086", + "\u3088", + "\u3089", + "\u308a", + "\u308b", + "\u308c", + "\u308d", + "\u3092", + "\u3093", + "\u30a1", + "\u30a2", + "\u30a3", + "\u30a4", + "\u30a6", + "\u30a7", + "\u30a8", + "\u30aa", + "\u30ab", + "\u30ad", + "\u30af", + "\u30b1", + "\u30b3", + "\u30b5", + "\u30b7", + "\u30b9", + "\u30bb", + "\u30bf", + "\u30c1", + "\u30c3", + "\u30c4", + "\u30c6", + "\u30c8", + "\u30ca", + "\u30cb", + "\u30ce", + "\u30cf", + "\u30d2", + "\u30d5", + "\u30d8", + "\u30db", + "\u30de", + "\u30df", + "\u30e0", + "\u30e1", + "\u30e2", + "\u30e3", + "\u30e5", + "\u30e7", + "\u30e9", + "\u30ea", + "\u30eb", + "\u30ec", + "\u30ed", + "\u30ef", + "\u30f3", + "\u30fb", + "\u30fc", + "\u4e00", + "\u4e09", + "\u4e0a", + "\u4e0b", + "\u4e0d", + "\u4e16", + "\u4e2d", + "\u4e3b", + "\u4e45", + "\u4e4b", + "\u4e5f", + "\u4e8b", + "\u4e8c", + "\u4e94", + "\u4e95", + "\u4eac", + "\u4eba", + "\u4ebb", + "\u4ec1", + "\u4ecb", + "\u4ee3", + "\u4eee", + "\u4f0a", + "\u4f1a", + "\u4f50", + "\u4f8d", + "\u4fdd", + "\u4fe1", + "\u5065", + "\u5143", + "\u5149", + "\u516b", + "\u516c", + "\u5185", + "\u51fa", + "\u5206", + "\u524d", + "\u5289", + "\u529b", + "\u52a0", + "\u52dd", + "\u5317", + "\u533a", + "\u5341", + "\u5343", + "\u5357", + "\u535a", + "\u539f", + "\u53e3", + "\u53e4", + "\u53f2", + "\u53f8", + "\u5408", + "\u5409", + "\u540c", + "\u540d", + "\u548c", + "\u56d7", + "\u56db", + "\u56fd", + "\u570b", + "\u571f", + "\u5730", + "\u5742", + "\u57ce", + "\u5802", + "\u5834", + "\u58eb", + "\u590f", + "\u5916", + "\u5927", + "\u5929", + "\u592a", + "\u592b", + "\u5948", + "\u5973", + "\u5b50", + "\u5b66", + "\u5b80", + "\u5b87", + "\u5b89", + "\u5b97", + "\u5b9a", + "\u5ba3", + "\u5bae", + "\u5bb6", + "\u5bbf", + "\u5bfa", + "\u5c07", + "\u5c0f", + "\u5c1a", + "\u5c71", + "\u5ca1", + "\u5cf6", + "\u5d0e", + "\u5ddd", + "\u5dde", + "\u5dff", + "\u5e1d", + "\u5e73", + "\u5e74", + "\u5e78", + "\u5e7f", + "\u5f18", + "\u5f35", + "\u5f73", + "\u5f8c", + "\u5fa1", + "\u5fb7", + "\u5fc3", + "\u5fc4", + "\u5fd7", + "\u5fe0", + "\u611b", + "\u6210", + "\u6211", + "\u6226", + "\u6238", + "\u624b", + "\u624c", + "\u653f", + "\u6587", + "\u65b0", + "\u65b9", + "\u65e5", + "\u660e", + "\u661f", + "\u6625", + "\u662d", + "\u667a", + "\u66f2", + "\u66f8", + "\u6708", + "\u6709", + "\u671d", + "\u6728", + "\u672c", + "\u674e", + "\u6751", + "\u6771", + "\u677e", + "\u6797", + "\u68ee", + "\u694a", + "\u6a39", + "\u6a4b", + "\u6b4c", + "\u6b62", + "\u6b63", + "\u6b66", + "\u6bd4", + "\u6c0f", + "\u6c11", + "\u6c34", + "\u6c35", + "\u6c37", + "\u6c38", + "\u6c5f", + "\u6ca2", + "\u6cb3", + "\u6cbb", + "\u6cd5", + "\u6d77", + "\u6e05", + "\u6f22", + "\u702c", + "\u706b", + "\u7248", + "\u72ac", + "\u738b", + "\u751f", + "\u7530", + "\u7537", + "\u7592", + "\u767a", + "\u767d", + "\u7684", + "\u7687", + "\u76ee", + "\u76f8", + "\u7701", + "\u771f", + "\u77f3", + "\u793a", + "\u793e", + "\u795e", + "\u798f", + "\u79be", + "\u79c0", + "\u79cb", + "\u7a7a", + "\u7acb", + "\u7ae0", + "\u7af9", + "\u7cf9", + "\u7f8e", + "\u7fa9", + "\u8033", + "\u826f", + "\u8279", + "\u82b1", + "\u82f1", + "\u83ef", + "\u8449", + "\u85e4", + "\u884c", + "\u8857", + "\u897f", + "\u898b", + "\u8a01", + "\u8a9e", + "\u8c37", + "\u8c9d", + "\u8cb4", + "\u8eca", + "\u8ecd", + "\u8fb6", + "\u9053", + "\u90ce", + "\u90e1", + "\u90e8", + "\u90fd", + "\u91cc", + "\u91ce", + "\u91d1", + "\u9234", + "\u9547", + "\u9577", + "\u9580", + "\u9593", + "\u961d", + "\u963f", + "\u9673", + "\u967d", + "\u96c4", + "\u9752", + "\u9762", + "\u98a8", + "\u98df", + "\u9999", + "\u99ac", + "\u9ad8", + "\u9f8d", + "\u9fb8", + "\ufb01", + "\ufb02", + "\uff01", + "\uff08", + "\uff09", + "\uff0c", + "\uff0d", + "\uff0e", + "\uff0f", + "\uff1a", + "\uff1f", + "\uff5e", + "the", + "of", + "and", + "in", + "to", + "was", + "he", + "is", + "as", + "for", + "on", + "with", + "that", + "it", + "his", + "by", + "at", + "from", + "her", + "##s", + "she", + "you", + "had", + "an", + "were", + "but", + "be", + "this", + "are", + "not", + "my", + "they", + "one", + "which", + "or", + "have", + "him", + "me", + "first", + "all", + "also", + "their", + "has", + "up", + "who", + "out", + "been", + "when", + "after", + "there", + "into", + "new", + "two", + "its", + "##a", + "time", + "would", + "no", + "what", + "about", + "said", + "we", + "over", + "then", + "other", + "so", + "more", + "##e", + "can", + "if", + "like", + "back", + "them", + "only", + "some", + "could", + "##i", + "where", + "just", + "##ing", + "during", + "before", + "##n", + "do", + "##o", + "made", + "school", + "through", + "than", + "now", + "years", + "most", + "world", + "may", + "between", + "down", + "well", + "three", + "##d", + "year", + "while", + "will", + "##ed", + "##r", + "##y", + "later", + "##t", + "city", + "under", + "around", + "did", + "such", + "being", + "used", + "state", + "people", + "part", + "know", + "against", + "your", + "many", + "second", + "university", + "both", + "national", + "##er", + "these", + "don", + "known", + "off", + "way", + "until", + "re", + "how", + "even", + "get", + "head", + "...", + "didn", + "##ly", + "team", + "american", + "because", + "de", + "##l", + "born", + "united", + "film", + "since", + "still", + "long", + "work", + "south", + "us", + "became", + "any", + "high", + "again", + "day", + "family", + "see", + "right", + "man", + "eyes", + "house", + "season", + "war", + "states", + "including", + "took", + "life", + "north", + "same", + "each", + "called", + "name", + "much", + "place", + "however", + "go", + "four", + "group", + "another", + "found", + "won", + "area", + "here", + "going", + "10", + "away", + "series", + "left", + "home", + "music", + "best", + "make", + "hand", + "number", + "company", + "several", + "never", + "last", + "john", + "000", + "very", + "album", + "take", + "end", + "good", + "too", + "following", + "released", + "game", + "played", + "little", + "began", + "district", + "##m", + "old", + "want", + "those", + "side", + "held", + "own", + "early", + "county", + "ll", + "league", + "use", + "west", + "##u", + "face", + "think", + "##es", + "2010", + "government", + "##h", + "march", + "came", + "small", + "general", + "town", + "june", + "##on", + "line", + "based", + "something", + "##k", + "september", + "thought", + "looked", + "along", + "international", + "2011", + "air", + "july", + "club", + "went", + "january", + "october", + "our", + "august", + "april", + "york", + "12", + "few", + "2012", + "2008", + "east", + "show", + "member", + "college", + "2009", + "father", + "public", + "##us", + "come", + "men", + "five", + "set", + "station", + "church", + "##c", + "next", + "former", + "november", + "room", + "party", + "located", + "december", + "2013", + "age", + "got", + "2007", + "##g", + "system", + "let", + "love", + "2006", + "though", + "every", + "2014", + "look", + "song", + "water", + "century", + "without", + "body", + "black", + "night", + "within", + "great", + "women", + "single", + "ve", + "building", + "large", + "population", + "river", + "named", + "band", + "white", + "started", + "##an", + "once", + "15", + "20", + "should", + "18", + "2015", + "service", + "top", + "built", + "british", + "open", + "death", + "king", + "moved", + "local", + "times", + "children", + "february", + "book", + "why", + "11", + "door", + "need", + "president", + "order", + "final", + "road", + "wasn", + "although", + "due", + "major", + "died", + "village", + "third", + "knew", + "2016", + "asked", + "turned", + "st", + "wanted", + "say", + "##p", + "together", + "received", + "main", + "son", + "served", + "different", + "##en", + "behind", + "himself", + "felt", + "members", + "power", + "football", + "law", + "voice", + "play", + "##in", + "near", + "park", + "history", + "30", + "having", + "2005", + "16", + "##man", + "saw", + "mother", + "##al", + "army", + "point", + "front", + "help", + "english", + "street", + "art", + "late", + "hands", + "games", + "award", + "##ia", + "young", + "14", + "put", + "published", + "country", + "division", + "across", + "told", + "13", + "often", + "ever", + "french", + "london", + "center", + "six", + "red", + "2017", + "led", + "days", + "include", + "light", + "25", + "find", + "tell", + "among", + "species", + "really", + "according", + "central", + "half", + "2004", + "form", + "original", + "gave", + "office", + "making", + "enough", + "lost", + "full", + "opened", + "must", + "included", + "live", + "given", + "german", + "player", + "run", + "business", + "woman", + "community", + "cup", + "might", + "million", + "land", + "2000", + "court", + "development", + "17", + "short", + "round", + "ii", + "km", + "seen", + "class", + "story", + "always", + "become", + "sure", + "research", + "almost", + "director", + "council", + "la", + "##2", + "career", + "things", + "using", + "island", + "##z", + "couldn", + "car", + "##is", + "24", + "close", + "force", + "##1", + "better", + "free", + "support", + "control", + "field", + "students", + "2003", + "education", + "married", + "##b", + "nothing", + "worked", + "others", + "record", + "big", + "inside", + "level", + "anything", + "continued", + "give", + "james", + "##3", + "military", + "established", + "non", + "returned", + "feel", + "does", + "title", + "written", + "thing", + "feet", + "william", + "far", + "co", + "association", + "hard", + "already", + "2002", + "##ra", + "championship", + "human", + "western", + "100", + "##na", + "department", + "hall", + "role", + "various", + "production", + "21", + "19", + "heart", + "2001", + "living", + "fire", + "version", + "##ers", + "##f", + "television", + "royal", + "##4", + "produced", + "working", + "act", + "case", + "society", + "region", + "present", + "radio", + "period", + "looking", + "least", + "total", + "keep", + "england", + "wife", + "program", + "per", + "brother", + "mind", + "special", + "22", + "##le", + "am", + "works", + "soon", + "##6", + "political", + "george", + "services", + "taken", + "created", + "##7", + "further", + "able", + "reached", + "david", + "union", + "joined", + "upon", + "done", + "important", + "social", + "information", + "either", + "##ic", + "##x", + "appeared", + "position", + "ground", + "lead", + "rock", + "dark", + "election", + "23", + "board", + "france", + "hair", + "course", + "arms", + "site", + "police", + "girl", + "instead", + "real", + "sound", + "##v", + "words", + "moment", + "##te", + "someone", + "##8", + "summer", + "project", + "announced", + "san", + "less", + "wrote", + "past", + "followed", + "##5", + "blue", + "founded", + "al", + "finally", + "india", + "taking", + "records", + "america", + "##ne", + "1999", + "design", + "considered", + "northern", + "god", + "stop", + "battle", + "toward", + "european", + "outside", + "described", + "track", + "today", + "playing", + "language", + "28", + "call", + "26", + "heard", + "professional", + "low", + "australia", + "miles", + "california", + "win", + "yet", + "green", + "##ie", + "trying", + "blood", + "##ton", + "southern", + "science", + "maybe", + "everything", + "match", + "square", + "27", + "mouth", + "video", + "race", + "recorded", + "leave", + "above", + "##9", + "daughter", + "points", + "space", + "1998", + "museum", + "change", + "middle", + "common", + "##0", + "move", + "tv", + "post", + "##ta", + "lake", + "seven", + "tried", + "elected", + "closed", + "ten", + "paul", + "minister", + "##th", + "months", + "start", + "chief", + "return", + "canada", + "person", + "sea", + "release", + "similar", + "modern", + "brought", + "rest", + "hit", + "formed", + "mr", + "##la", + "1997", + "floor", + "event", + "doing", + "thomas", + "1996", + "robert", + "care", + "killed", + "training", + "star", + "week", + "needed", + "turn", + "finished", + "railway", + "rather", + "news", + "health", + "sent", + "example", + "ran", + "term", + "michael", + "coming", + "currently", + "yes", + "forces", + "despite", + "gold", + "areas", + "50", + "stage", + "fact", + "29", + "dead", + "says", + "popular", + "2018", + "originally", + "germany", + "probably", + "developed", + "result", + "pulled", + "friend", + "stood", + "money", + "running", + "mi", + "signed", + "word", + "songs", + "child", + "eventually", + "met", + "tour", + "average", + "teams", + "minutes", + "festival", + "current", + "deep", + "kind", + "1995", + "decided", + "usually", + "eastern", + "seemed", + "##ness", + "episode", + "bed", + "added", + "table", + "indian", + "private", + "charles", + "route", + "available", + "idea", + "throughout", + "centre", + "addition", + "appointed", + "style", + "1994", + "books", + "eight", + "construction", + "press", + "mean", + "wall", + "friends", + "remained", + "schools", + "study", + "##ch", + "##um", + "institute", + "oh", + "chinese", + "sometimes", + "events", + "possible", + "1992", + "australian", + "type", + "brown", + "forward", + "talk", + "process", + "food", + "debut", + "seat", + "performance", + "committee", + "features", + "character", + "arts", + "herself", + "else", + "lot", + "strong", + "russian", + "range", + "hours", + "peter", + "arm", + "##da", + "morning", + "dr", + "sold", + "##ry", + "quickly", + "directed", + "1993", + "guitar", + "china", + "##w", + "31", + "list", + "##ma", + "performed", + "media", + "uk", + "players", + "smile", + "##rs", + "myself", + "40", + "placed", + "coach", + "province", + "towards", + "wouldn", + "leading", + "whole", + "boy", + "official", + "designed", + "grand", + "census", + "##el", + "europe", + "attack", + "japanese", + "henry", + "1991", + "##re", + "##os", + "cross", + "getting", + "alone", + "action", + "lower", + "network", + "wide", + "washington", + "japan", + "1990", + "hospital", + "believe", + "changed", + "sister", + "##ar", + "hold", + "gone", + "sir", + "hadn", + "ship", + "##ka", + "studies", + "academy", + "shot", + "rights", + "below", + "base", + "bad", + "involved", + "kept", + "largest", + "##ist", + "bank", + "future", + "especially", + "beginning", + "mark", + "movement", + "section", + "female", + "magazine", + "plan", + "professor", + "lord", + "longer", + "##ian", + "sat", + "walked", + "hill", + "actually", + "civil", + "energy", + "model", + "families", + "size", + "thus", + "aircraft", + "completed", + "includes", + "data", + "captain", + "##or", + "fight", + "vocals", + "featured", + "richard", + "bridge", + "fourth", + "1989", + "officer", + "stone", + "hear", + "##ism", + "means", + "medical", + "groups", + "management", + "self", + "lips", + "competition", + "entire", + "lived", + "technology", + "leaving", + "federal", + "tournament", + "bit", + "passed", + "hot", + "independent", + "awards", + "kingdom", + "mary", + "spent", + "fine", + "doesn", + "reported", + "##ling", + "jack", + "fall", + "raised", + "itself", + "stay", + "true", + "studio", + "1988", + "sports", + "replaced", + "paris", + "systems", + "saint", + "leader", + "theatre", + "whose", + "market", + "capital", + "parents", + "spanish", + "canadian", + "earth", + "##ity", + "cut", + "degree", + "writing", + "bay", + "christian", + "awarded", + "natural", + "higher", + "bill", + "##as", + "coast", + "provided", + "previous", + "senior", + "ft", + "valley", + "organization", + "stopped", + "onto", + "countries", + "parts", + "conference", + "queen", + "security", + "interest", + "saying", + "allowed", + "master", + "earlier", + "phone", + "matter", + "smith", + "winning", + "try", + "happened", + "moving", + "campaign", + "los", + "##ley", + "breath", + "nearly", + "mid", + "1987", + "certain", + "girls", + "date", + "italian", + "african", + "standing", + "fell", + "artist", + "##ted", + "shows", + "deal", + "mine", + "industry", + "1986", + "##ng", + "everyone", + "republic", + "provide", + "collection", + "library", + "student", + "##ville", + "primary", + "owned", + "older", + "via", + "heavy", + "1st", + "makes", + "##able", + "attention", + "anyone", + "africa", + "##ri", + "stated", + "length", + "ended", + "fingers", + "command", + "staff", + "skin", + "foreign", + "opening", + "governor", + "okay", + "medal", + "kill", + "sun", + "cover", + "job", + "1985", + "introduced", + "chest", + "hell", + "feeling", + "##ies", + "success", + "meet", + "reason", + "standard", + "meeting", + "novel", + "1984", + "trade", + "source", + "buildings", + "##land", + "rose", + "guy", + "goal", + "##ur", + "chapter", + "native", + "husband", + "previously", + "unit", + "limited", + "entered", + "weeks", + "producer", + "operations", + "mountain", + "takes", + "covered", + "forced", + "related", + "roman", + "complete", + "successful", + "key", + "texas", + "cold", + "##ya", + "channel", + "1980", + "traditional", + "films", + "dance", + "clear", + "approximately", + "500", + "nine", + "van", + "prince", + "question", + "active", + "tracks", + "ireland", + "regional", + "silver", + "author", + "personal", + "sense", + "operation", + "##ine", + "economic", + "1983", + "holding", + "twenty", + "isbn", + "additional", + "speed", + "hour", + "edition", + "regular", + "historic", + "places", + "whom", + "shook", + "movie", + "km\u00b2", + "secretary", + "prior", + "report", + "chicago", + "read", + "foundation", + "view", + "engine", + "scored", + "1982", + "units", + "ask", + "airport", + "property", + "ready", + "immediately", + "lady", + "month", + "listed", + "contract", + "##de", + "manager", + "themselves", + "lines", + "##ki", + "navy", + "writer", + "meant", + "##ts", + "runs", + "##ro", + "practice", + "championships", + "singer", + "glass", + "commission", + "required", + "forest", + "starting", + "culture", + "generally", + "giving", + "access", + "attended", + "test", + "couple", + "stand", + "catholic", + "martin", + "caught", + "executive", + "##less", + "eye", + "##ey", + "thinking", + "chair", + "quite", + "shoulder", + "1979", + "hope", + "decision", + "plays", + "defeated", + "municipality", + "whether", + "structure", + "offered", + "slowly", + "pain", + "ice", + "direction", + "##ion", + "paper", + "mission", + "1981", + "mostly", + "200", + "noted", + "individual", + "managed", + "nature", + "lives", + "plant", + "##ha", + "helped", + "except", + "studied", + "computer", + "figure", + "relationship", + "issue", + "significant", + "loss", + "die", + "smiled", + "gun", + "ago", + "highest", + "1972", + "##am", + "male", + "bring", + "goals", + "mexico", + "problem", + "distance", + "commercial", + "completely", + "location", + "annual", + "famous", + "drive", + "1976", + "neck", + "1978", + "surface", + "caused", + "italy", + "understand", + "greek", + "highway", + "wrong", + "hotel", + "comes", + "appearance", + "joseph", + "double", + "issues", + "musical", + "companies", + "castle", + "income", + "review", + "assembly", + "bass", + "initially", + "parliament", + "artists", + "experience", + "1974", + "particular", + "walk", + "foot", + "engineering", + "talking", + "window", + "dropped", + "##ter", + "miss", + "baby", + "boys", + "break", + "1975", + "stars", + "edge", + "remember", + "policy", + "carried", + "train", + "stadium", + "bar", + "sex", + "angeles", + "evidence", + "##ge", + "becoming", + "assistant", + "soviet", + "1977", + "upper", + "step", + "wing", + "1970", + "youth", + "financial", + "reach", + "##ll", + "actor", + "numerous", + "##se", + "##st", + "nodded", + "arrived", + "##ation", + "minute", + "##nt", + "believed", + "sorry", + "complex", + "beautiful", + "victory", + "associated", + "temple", + "1968", + "1973", + "chance", + "perhaps", + "metal", + "##son", + "1945", + "bishop", + "##et", + "lee", + "launched", + "particularly", + "tree", + "le", + "retired", + "subject", + "prize", + "contains", + "yeah", + "theory", + "empire", + "##ce", + "suddenly", + "waiting", + "trust", + "recording", + "##to", + "happy", + "terms", + "camp", + "champion", + "1971", + "religious", + "pass", + "zealand", + "names", + "2nd", + "port", + "ancient", + "tom", + "corner", + "represented", + "watch", + "legal", + "anti", + "justice", + "cause", + "watched", + "brothers", + "45", + "material", + "changes", + "simply", + "response", + "louis", + "fast", + "##ting", + "answer", + "60", + "historical", + "1969", + "stories", + "straight", + "create", + "feature", + "increased", + "rate", + "administration", + "virginia", + "el", + "activities", + "cultural", + "overall", + "winner", + "programs", + "basketball", + "legs", + "guard", + "beyond", + "cast", + "doctor", + "mm", + "flight", + "results", + "remains", + "cost", + "effect", + "winter", + "##ble", + "larger", + "islands", + "problems", + "chairman", + "grew", + "commander", + "isn", + "1967", + "pay", + "failed", + "selected", + "hurt", + "fort", + "box", + "regiment", + "majority", + "journal", + "35", + "edward", + "plans", + "##ke", + "##ni", + "shown", + "pretty", + "irish", + "characters", + "directly", + "scene", + "likely", + "operated", + "allow", + "spring", + "##j", + "junior", + "matches", + "looks", + "mike", + "houses", + "fellow", + "##tion", + "beach", + "marriage", + "##ham", + "##ive", + "rules", + "oil", + "65", + "florida", + "expected", + "nearby", + "congress", + "sam", + "peace", + "recent", + "iii", + "wait", + "subsequently", + "cell", + "##do", + "variety", + "serving", + "agreed", + "please", + "poor", + "joe", + "pacific", + "attempt", + "wood", + "democratic", + "piece", + "prime", + "##ca", + "rural", + "mile", + "touch", + "appears", + "township", + "1964", + "1966", + "soldiers", + "##men", + "##ized", + "1965", + "pennsylvania", + "closer", + "fighting", + "claimed", + "score", + "jones", + "physical", + "editor", + "##ous", + "filled", + "genus", + "specific", + "sitting", + "super", + "mom", + "##va", + "therefore", + "supported", + "status", + "fear", + "cases", + "store", + "meaning", + "wales", + "minor", + "spain", + "tower", + "focus", + "vice", + "frank", + "follow", + "parish", + "separate", + "golden", + "horse", + "fifth", + "remaining", + "branch", + "32", + "presented", + "stared", + "##id", + "uses", + "secret", + "forms", + "##co", + "baseball", + "exactly", + "##ck", + "choice", + "note", + "discovered", + "travel", + "composed", + "truth", + "russia", + "ball", + "color", + "kiss", + "dad", + "wind", + "continue", + "ring", + "referred", + "numbers", + "digital", + "greater", + "##ns", + "metres", + "slightly", + "direct", + "increase", + "1960", + "responsible", + "crew", + "rule", + "trees", + "troops", + "##no", + "broke", + "goes", + "individuals", + "hundred", + "weight", + "creek", + "sleep", + "memory", + "defense", + "provides", + "ordered", + "code", + "value", + "jewish", + "windows", + "1944", + "safe", + "judge", + "whatever", + "corps", + "realized", + "growing", + "pre", + "##ga", + "cities", + "alexander", + "gaze", + "lies", + "spread", + "scott", + "letter", + "showed", + "situation", + "mayor", + "transport", + "watching", + "workers", + "extended", + "##li", + "expression", + "normal", + "##ment", + "chart", + "multiple", + "border", + "##ba", + "host", + "##ner", + "daily", + "mrs", + "walls", + "piano", + "##ko", + "heat", + "cannot", + "##ate", + "earned", + "products", + "drama", + "era", + "authority", + "seasons", + "join", + "grade", + "##io", + "sign", + "difficult", + "machine", + "1963", + "territory", + "mainly", + "##wood", + "stations", + "squadron", + "1962", + "stepped", + "iron", + "19th", + "##led", + "serve", + "appear", + "sky", + "speak", + "broken", + "charge", + "knowledge", + "kilometres", + "removed", + "ships", + "article", + "campus", + "simple", + "##ty", + "pushed", + "britain", + "##ve", + "leaves", + "recently", + "cd", + "soft", + "boston", + "latter", + "easy", + "acquired", + "poland", + "##sa", + "quality", + "officers", + "presence", + "planned", + "nations", + "mass", + "broadcast", + "jean", + "share", + "image", + "influence", + "wild", + "offer", + "emperor", + "electric", + "reading", + "headed", + "ability", + "promoted", + "yellow", + "ministry", + "1942", + "throat", + "smaller", + "politician", + "##by", + "latin", + "spoke", + "cars", + "williams", + "males", + "lack", + "pop", + "80", + "##ier", + "acting", + "seeing", + "consists", + "##ti", + "estate", + "1961", + "pressure", + "johnson", + "newspaper", + "jr", + "chris", + "olympics", + "online", + "conditions", + "beat", + "elements", + "walking", + "vote", + "##field", + "needs", + "carolina", + "text", + "featuring", + "global", + "block", + "shirt", + "levels", + "francisco", + "purpose", + "females", + "et", + "dutch", + "duke", + "ahead", + "gas", + "twice", + "safety", + "serious", + "turning", + "highly", + "lieutenant", + "firm", + "maria", + "amount", + "mixed", + "daniel", + "proposed", + "perfect", + "agreement", + "affairs", + "3rd", + "seconds", + "contemporary", + "paid", + "1943", + "prison", + "save", + "kitchen", + "label", + "administrative", + "intended", + "constructed", + "academic", + "nice", + "teacher", + "races", + "1956", + "formerly", + "corporation", + "ben", + "nation", + "issued", + "shut", + "1958", + "drums", + "housing", + "victoria", + "seems", + "opera", + "1959", + "graduated", + "function", + "von", + "mentioned", + "picked", + "build", + "recognized", + "shortly", + "protection", + "picture", + "notable", + "exchange", + "elections", + "1980s", + "loved", + "percent", + "racing", + "fish", + "elizabeth", + "garden", + "volume", + "hockey", + "1941", + "beside", + "settled", + "##ford", + "1940", + "competed", + "replied", + "drew", + "1948", + "actress", + "marine", + "scotland", + "steel", + "glanced", + "farm", + "steve", + "1957", + "risk", + "tonight", + "positive", + "magic", + "singles", + "effects", + "gray", + "screen", + "dog", + "##ja", + "residents", + "bus", + "sides", + "none", + "secondary", + "literature", + "polish", + "destroyed", + "flying", + "founder", + "households", + "1939", + "lay", + "reserve", + "usa", + "gallery", + "##ler", + "1946", + "industrial", + "younger", + "approach", + "appearances", + "urban", + "ones", + "1950", + "finish", + "avenue", + "powerful", + "fully", + "growth", + "page", + "honor", + "jersey", + "projects", + "advanced", + "revealed", + "basic", + "90", + "infantry", + "pair", + "equipment", + "visit", + "33", + "evening", + "search", + "grant", + "effort", + "solo", + "treatment", + "buried", + "republican", + "primarily", + "bottom", + "owner", + "1970s", + "israel", + "gives", + "jim", + "dream", + "bob", + "remain", + "spot", + "70", + "notes", + "produce", + "champions", + "contact", + "ed", + "soul", + "accepted", + "ways", + "del", + "##ally", + "losing", + "split", + "price", + "capacity", + "basis", + "trial", + "questions", + "##ina", + "1955", + "20th", + "guess", + "officially", + "memorial", + "naval", + "initial", + "##ization", + "whispered", + "median", + "engineer", + "##ful", + "sydney", + "##go", + "columbia", + "strength", + "300", + "1952", + "tears", + "senate", + "00", + "card", + "asian", + "agent", + "1947", + "software", + "44", + "draw", + "warm", + "supposed", + "com", + "pro", + "##il", + "transferred", + "leaned", + "##at", + "candidate", + "escape", + "mountains", + "asia", + "potential", + "activity", + "entertainment", + "seem", + "traffic", + "jackson", + "murder", + "36", + "slow", + "product", + "orchestra", + "haven", + "agency", + "bbc", + "taught", + "website", + "comedy", + "unable", + "storm", + "planning", + "albums", + "rugby", + "environment", + "scientific", + "grabbed", + "protect", + "##hi", + "boat", + "typically", + "1954", + "1953", + "damage", + "principal", + "divided", + "dedicated", + "mount", + "ohio", + "##berg", + "pick", + "fought", + "driver", + "##der", + "empty", + "shoulders", + "sort", + "thank", + "berlin", + "prominent", + "account", + "freedom", + "necessary", + "efforts", + "alex", + "headquarters", + "follows", + "alongside", + "des", + "simon", + "andrew", + "suggested", + "operating", + "learning", + "steps", + "1949", + "sweet", + "technical", + "begin", + "easily", + "34", + "teeth", + "speaking", + "settlement", + "scale", + "##sh", + "renamed", + "ray", + "max", + "enemy", + "semi", + "joint", + "compared", + "##rd", + "scottish", + "leadership", + "analysis", + "offers", + "georgia", + "pieces", + "captured", + "animal", + "deputy", + "guest", + "organized", + "##lin", + "tony", + "combined", + "method", + "challenge", + "1960s", + "huge", + "wants", + "battalion", + "sons", + "rise", + "crime", + "types", + "facilities", + "telling", + "path", + "1951", + "platform", + "sit", + "1990s", + "##lo", + "tells", + "assigned", + "rich", + "pull", + "##ot", + "commonly", + "alive", + "##za", + "letters", + "concept", + "conducted", + "wearing", + "happen", + "bought", + "becomes", + "holy", + "gets", + "ocean", + "defeat", + "languages", + "purchased", + "coffee", + "occurred", + "titled", + "##q", + "declared", + "applied", + "sciences", + "concert", + "sounds", + "jazz", + "brain", + "##me", + "painting", + "fleet", + "tax", + "nick", + "##ius", + "michigan", + "count", + "animals", + "leaders", + "episodes", + "##line", + "content", + "##den", + "birth", + "##it", + "clubs", + "64", + "palace", + "critical", + "refused", + "fair", + "leg", + "laughed", + "returning", + "surrounding", + "participated", + "formation", + "lifted", + "pointed", + "connected", + "rome", + "medicine", + "laid", + "taylor", + "santa", + "powers", + "adam", + "tall", + "shared", + "focused", + "knowing", + "yards", + "entrance", + "falls", + "##wa", + "calling", + "##ad", + "sources", + "chosen", + "beneath", + "resources", + "yard", + "##ite", + "nominated", + "silence", + "zone", + "defined", + "##que", + "gained", + "thirty", + "38", + "bodies", + "moon", + "##ard", + "adopted", + "christmas", + "widely", + "register", + "apart", + "iran", + "premier", + "serves", + "du", + "unknown", + "parties", + "##les", + "generation", + "##ff", + "continues", + "quick", + "fields", + "brigade", + "quiet", + "teaching", + "clothes", + "impact", + "weapons", + "partner", + "flat", + "theater", + "supreme", + "1938", + "37", + "relations", + "##tor", + "plants", + "suffered", + "1936", + "wilson", + "kids", + "begins", + "##age", + "1918", + "seats", + "armed", + "internet", + "models", + "worth", + "laws", + "400", + "communities", + "classes", + "background", + "knows", + "thanks", + "quarter", + "reaching", + "humans", + "carry", + "killing", + "format", + "kong", + "hong", + "setting", + "75", + "architecture", + "disease", + "railroad", + "inc", + "possibly", + "wish", + "arthur", + "thoughts", + "harry", + "doors", + "density", + "##di", + "crowd", + "illinois", + "stomach", + "tone", + "unique", + "reports", + "anyway", + "##ir", + "liberal", + "der", + "vehicle", + "thick", + "dry", + "drug", + "faced", + "largely", + "facility", + "theme", + "holds", + "creation", + "strange", + "colonel", + "##mi", + "revolution", + "bell", + "politics", + "turns", + "silent", + "rail", + "relief", + "independence", + "combat", + "shape", + "write", + "determined", + "sales", + "learned", + "4th", + "finger", + "oxford", + "providing", + "1937", + "heritage", + "fiction", + "situated", + "designated", + "allowing", + "distribution", + "hosted", + "##est", + "sight", + "interview", + "estimated", + "reduced", + "##ria", + "toronto", + "footballer", + "keeping", + "guys", + "damn", + "claim", + "motion", + "sport", + "sixth", + "stayed", + "##ze", + "en", + "rear", + "receive", + "handed", + "twelve", + "dress", + "audience", + "granted", + "brazil", + "##well", + "spirit", + "##ated", + "noticed", + "etc", + "olympic", + "representative", + "eric", + "tight", + "trouble", + "reviews", + "drink", + "vampire", + "missing", + "roles", + "ranked", + "newly", + "household", + "finals", + "wave", + "critics", + "##ee", + "phase", + "massachusetts", + "pilot", + "unlike", + "philadelphia", + "bright", + "guns", + "crown", + "organizations", + "roof", + "42", + "respectively", + "clearly", + "tongue", + "marked", + "circle", + "fox", + "korea", + "bronze", + "brian", + "expanded", + "sexual", + "supply", + "yourself", + "inspired", + "labour", + "fc", + "##ah", + "reference", + "vision", + "draft", + "connection", + "brand", + "reasons", + "1935", + "classic", + "driving", + "trip", + "jesus", + "cells", + "entry", + "1920", + "neither", + "trail", + "claims", + "atlantic", + "orders", + "labor", + "nose", + "afraid", + "identified", + "intelligence", + "calls", + "cancer", + "attacked", + "passing", + "stephen", + "positions", + "imperial", + "grey", + "jason", + "39", + "sunday", + "48", + "swedish", + "avoid", + "extra", + "uncle", + "message", + "covers", + "allows", + "surprise", + "materials", + "fame", + "hunter", + "##ji", + "1930", + "citizens", + "figures", + "davis", + "environmental", + "confirmed", + "shit", + "titles", + "di", + "performing", + "difference", + "acts", + "attacks", + "##ov", + "existing", + "votes", + "opportunity", + "nor", + "shop", + "entirely", + "trains", + "opposite", + "pakistan", + "##pa", + "develop", + "resulted", + "representatives", + "actions", + "reality", + "pressed", + "##ish", + "barely", + "wine", + "conversation", + "faculty", + "northwest", + "ends", + "documentary", + "nuclear", + "stock", + "grace", + "sets", + "eat", + "alternative", + "##ps", + "bag", + "resulting", + "creating", + "surprised", + "cemetery", + "1919", + "drop", + "finding", + "sarah", + "cricket", + "streets", + "tradition", + "ride", + "1933", + "exhibition", + "target", + "ear", + "explained", + "rain", + "composer", + "injury", + "apartment", + "municipal", + "educational", + "occupied", + "netherlands", + "clean", + "billion", + "constitution", + "learn", + "1914", + "maximum", + "classical", + "francis", + "lose", + "opposition", + "jose", + "ontario", + "bear", + "core", + "hills", + "rolled", + "ending", + "drawn", + "permanent", + "fun", + "##tes", + "##lla", + "lewis", + "sites", + "chamber", + "ryan", + "##way", + "scoring", + "height", + "1934", + "##house", + "lyrics", + "staring", + "55", + "officials", + "1917", + "snow", + "oldest", + "##tic", + "orange", + "##ger", + "qualified", + "interior", + "apparently", + "succeeded", + "thousand", + "dinner", + "lights", + "existence", + "fans", + "heavily", + "41", + "greatest", + "conservative", + "send", + "bowl", + "plus", + "enter", + "catch", + "##un", + "economy", + "duty", + "1929", + "speech", + "authorities", + "princess", + "performances", + "versions", + "shall", + "graduate", + "pictures", + "effective", + "remembered", + "poetry", + "desk", + "crossed", + "starring", + "starts", + "passenger", + "sharp", + "##ant", + "acres", + "ass", + "weather", + "falling", + "rank", + "fund", + "supporting", + "check", + "adult", + "publishing", + "heads", + "cm", + "southeast", + "lane", + "##burg", + "application", + "bc", + "##ura", + "les", + "condition", + "transfer", + "prevent", + "display", + "ex", + "regions", + "earl", + "federation", + "cool", + "relatively", + "answered", + "besides", + "1928", + "obtained", + "portion", + "##town", + "mix", + "##ding", + "reaction", + "liked", + "dean", + "express", + "peak", + "1932", + "##tte", + "counter", + "religion", + "chain", + "rare", + "miller", + "convention", + "aid", + "lie", + "vehicles", + "mobile", + "perform", + "squad", + "wonder", + "lying", + "crazy", + "sword", + "##ping", + "attempted", + "centuries", + "weren", + "philosophy", + "category", + "##ize", + "anna", + "interested", + "47", + "sweden", + "wolf", + "frequently", + "abandoned", + "kg", + "literary", + "alliance", + "task", + "entitled", + "##ay", + "threw", + "promotion", + "factory", + "tiny", + "soccer", + "visited", + "matt", + "fm", + "achieved", + "52", + "defence", + "internal", + "persian", + "43", + "methods", + "##ging", + "arrested", + "otherwise", + "cambridge", + "programming", + "villages", + "elementary", + "districts", + "rooms", + "criminal", + "conflict", + "worry", + "trained", + "1931", + "attempts", + "waited", + "signal", + "bird", + "truck", + "subsequent", + "programme", + "##ol", + "ad", + "49", + "communist", + "details", + "faith", + "sector", + "patrick", + "carrying", + "laugh", + "##ss", + "controlled", + "korean", + "showing", + "origin", + "fuel", + "evil", + "1927", + "##ent", + "brief", + "identity", + "darkness", + "address", + "pool", + "missed", + "publication", + "web", + "planet", + "ian", + "anne", + "wings", + "invited", + "##tt", + "briefly", + "standards", + "kissed", + "##be", + "ideas", + "climate", + "causing", + "walter", + "worse", + "albert", + "articles", + "winners", + "desire", + "aged", + "northeast", + "dangerous", + "gate", + "doubt", + "1922", + "wooden", + "multi", + "##ky", + "poet", + "rising", + "funding", + "46", + "communications", + "communication", + "violence", + "copies", + "prepared", + "ford", + "investigation", + "skills", + "1924", + "pulling", + "electronic", + "##ak", + "##ial", + "##han", + "containing", + "ultimately", + "offices", + "singing", + "understanding", + "restaurant", + "tomorrow", + "fashion", + "christ", + "ward", + "da", + "pope", + "stands", + "5th", + "flow", + "studios", + "aired", + "commissioned", + "contained", + "exist", + "fresh", + "americans", + "##per", + "wrestling", + "approved", + "kid", + "employed", + "respect", + "suit", + "1925", + "angel", + "asking", + "increasing", + "frame", + "angry", + "selling", + "1950s", + "thin", + "finds", + "##nd", + "temperature", + "statement", + "ali", + "explain", + "inhabitants", + "towns", + "extensive", + "narrow", + "51", + "jane", + "flowers", + "images", + "promise", + "somewhere", + "object", + "fly", + "closely", + "##ls", + "1912", + "bureau", + "cape", + "1926", + "weekly", + "presidential", + "legislative", + "1921", + "##ai", + "##au", + "launch", + "founding", + "##ny", + "978", + "##ring", + "artillery", + "strike", + "un", + "institutions", + "roll", + "writers", + "landing", + "chose", + "kevin", + "anymore", + "pp", + "##ut", + "attorney", + "fit", + "dan", + "billboard", + "receiving", + "agricultural", + "breaking", + "sought", + "dave", + "admitted", + "lands", + "mexican", + "##bury", + "charlie", + "specifically", + "hole", + "iv", + "howard", + "credit", + "moscow", + "roads", + "accident", + "1923", + "proved", + "wear", + "struck", + "hey", + "guards", + "stuff", + "slid", + "expansion", + "1915", + "cat", + "anthony", + "##kin", + "melbourne", + "opposed", + "sub", + "southwest", + "architect", + "failure", + "plane", + "1916", + "##ron", + "map", + "camera", + "tank", + "listen", + "regarding", + "wet", + "introduction", + "metropolitan", + "link", + "ep", + "fighter", + "inch", + "grown", + "gene", + "anger", + "fixed", + "buy", + "dvd", + "khan", + "domestic", + "worldwide", + "chapel", + "mill", + "functions", + "examples", + "##head", + "developing", + "1910", + "turkey", + "hits", + "pocket", + "antonio", + "papers", + "grow", + "unless", + "circuit", + "18th", + "concerned", + "attached", + "journalist", + "selection", + "journey", + "converted", + "provincial", + "painted", + "hearing", + "aren", + "bands", + "negative", + "aside", + "wondered", + "knight", + "lap", + "survey", + "ma", + "##ow", + "noise", + "billy", + "##ium", + "shooting", + "guide", + "bedroom", + "priest", + "resistance", + "motor", + "homes", + "sounded", + "giant", + "##mer", + "150", + "scenes", + "equal", + "comic", + "patients", + "hidden", + "solid", + "actual", + "bringing", + "afternoon", + "touched", + "funds", + "wedding", + "consisted", + "marie", + "canal", + "sr", + "kim", + "treaty", + "turkish", + "recognition", + "residence", + "cathedral", + "broad", + "knees", + "incident", + "shaped", + "fired", + "norwegian", + "handle", + "cheek", + "contest", + "represent", + "##pe", + "representing", + "beauty", + "##sen", + "birds", + "advantage", + "emergency", + "wrapped", + "drawing", + "notice", + "pink", + "broadcasting", + "##ong", + "somehow", + "bachelor", + "seventh", + "collected", + "registered", + "establishment", + "alan", + "assumed", + "chemical", + "personnel", + "roger", + "retirement", + "jeff", + "portuguese", + "wore", + "tied", + "device", + "threat", + "progress", + "advance", + "##ised", + "banks", + "hired", + "manchester", + "nfl", + "teachers", + "structures", + "forever", + "##bo", + "tennis", + "helping", + "saturday", + "sale", + "applications", + "junction", + "hip", + "incorporated", + "neighborhood", + "dressed", + "ceremony", + "##ds", + "influenced", + "hers", + "visual", + "stairs", + "decades", + "inner", + "kansas", + "hung", + "hoped", + "gain", + "scheduled", + "downtown", + "engaged", + "austria", + "clock", + "norway", + "certainly", + "pale", + "protected", + "1913", + "victor", + "employees", + "plate", + "putting", + "surrounded", + "##ists", + "finishing", + "blues", + "tropical", + "##ries", + "minnesota", + "consider", + "philippines", + "accept", + "54", + "retrieved", + "1900", + "concern", + "anderson", + "properties", + "institution", + "gordon", + "successfully", + "vietnam", + "##dy", + "backing", + "outstanding", + "muslim", + "crossing", + "folk", + "producing", + "usual", + "demand", + "occurs", + "observed", + "lawyer", + "educated", + "##ana", + "kelly", + "string", + "pleasure", + "budget", + "items", + "quietly", + "colorado", + "philip", + "typical", + "##worth", + "derived", + "600", + "survived", + "asks", + "mental", + "##ide", + "56", + "jake", + "jews", + "distinguished", + "ltd", + "1911", + "sri", + "extremely", + "53", + "athletic", + "loud", + "thousands", + "worried", + "shadow", + "transportation", + "horses", + "weapon", + "arena", + "importance", + "users", + "tim", + "objects", + "contributed", + "dragon", + "douglas", + "aware", + "senator", + "johnny", + "jordan", + "sisters", + "engines", + "flag", + "investment", + "samuel", + "shock", + "capable", + "clark", + "row", + "wheel", + "refers", + "session", + "familiar", + "biggest", + "wins", + "hate", + "maintained", + "drove", + "hamilton", + "request", + "expressed", + "injured", + "underground", + "churches", + "walker", + "wars", + "tunnel", + "passes", + "stupid", + "agriculture", + "softly", + "cabinet", + "regarded", + "joining", + "indiana", + "##ea", + "##ms", + "push", + "dates", + "spend", + "behavior", + "woods", + "protein", + "gently", + "chase", + "morgan", + "mention", + "burning", + "wake", + "combination", + "occur", + "mirror", + "leads", + "jimmy", + "indeed", + "impossible", + "singapore", + "paintings", + "covering", + "##nes", + "soldier", + "locations", + "attendance", + "sell", + "historian", + "wisconsin", + "invasion", + "argued", + "painter", + "diego", + "changing", + "egypt", + "##don", + "experienced", + "inches", + "##ku", + "missouri", + "vol", + "grounds", + "spoken", + "switzerland", + "##gan", + "reform", + "rolling", + "ha", + "forget", + "massive", + "resigned", + "burned", + "allen", + "tennessee", + "locked", + "values", + "improved", + "##mo", + "wounded", + "universe", + "sick", + "dating", + "facing", + "pack", + "purchase", + "user", + "##pur", + "moments", + "##ul", + "merged", + "anniversary", + "1908", + "coal", + "brick", + "understood", + "causes", + "dynasty", + "queensland", + "establish", + "stores", + "crisis", + "promote", + "hoping", + "views", + "cards", + "referee", + "extension", + "##si", + "raise", + "arizona", + "improve", + "colonial", + "formal", + "charged", + "##rt", + "palm", + "lucky", + "hide", + "rescue", + "faces", + "95", + "feelings", + "candidates", + "juan", + "##ell", + "goods", + "6th", + "courses", + "weekend", + "59", + "luke", + "cash", + "fallen", + "##om", + "delivered", + "affected", + "installed", + "carefully", + "tries", + "swiss", + "hollywood", + "costs", + "lincoln", + "responsibility", + "##he", + "shore", + "file", + "proper", + "normally", + "maryland", + "assistance", + "jump", + "constant", + "offering", + "friendly", + "waters", + "persons", + "realize", + "contain", + "trophy", + "800", + "partnership", + "factor", + "58", + "musicians", + "cry", + "bound", + "oregon", + "indicated", + "hero", + "houston", + "medium", + "##ure", + "consisting", + "somewhat", + "##ara", + "57", + "cycle", + "##che", + "beer", + "moore", + "frederick", + "gotten", + "eleven", + "worst", + "weak", + "approached", + "arranged", + "chin", + "loan", + "universal", + "bond", + "fifteen", + "pattern", + "disappeared", + "##ney", + "translated", + "##zed", + "lip", + "arab", + "capture", + "interests", + "insurance", + "##chi", + "shifted", + "cave", + "prix", + "warning", + "sections", + "courts", + "coat", + "plot", + "smell", + "feed", + "golf", + "favorite", + "maintain", + "knife", + "vs", + "voted", + "degrees", + "finance", + "quebec", + "opinion", + "translation", + "manner", + "ruled", + "operate", + "productions", + "choose", + "musician", + "discovery", + "confused", + "tired", + "separated", + "stream", + "techniques", + "committed", + "attend", + "ranking", + "kings", + "throw", + "passengers", + "measure", + "horror", + "fan", + "mining", + "sand", + "danger", + "salt", + "calm", + "decade", + "dam", + "require", + "runner", + "##ik", + "rush", + "associate", + "greece", + "##ker", + "rivers", + "consecutive", + "matthew", + "##ski", + "sighed", + "sq", + "documents", + "steam", + "edited", + "closing", + "tie", + "accused", + "1905", + "##ini", + "islamic", + "distributed", + "directors", + "organisation", + "bruce", + "7th", + "breathing", + "mad", + "lit", + "arrival", + "concrete", + "taste", + "08", + "composition", + "shaking", + "faster", + "amateur", + "adjacent", + "stating", + "1906", + "twin", + "flew", + "##ran", + "tokyo", + "publications", + "##tone", + "obviously", + "ridge", + "storage", + "1907", + "carl", + "pages", + "concluded", + "desert", + "driven", + "universities", + "ages", + "terminal", + "sequence", + "borough", + "250", + "constituency", + "creative", + "cousin", + "economics", + "dreams", + "margaret", + "notably", + "reduce", + "montreal", + "mode", + "17th", + "ears", + "saved", + "jan", + "vocal", + "##ica", + "1909", + "andy", + "##jo", + "riding", + "roughly", + "threatened", + "##ise", + "meters", + "meanwhile", + "landed", + "compete", + "repeated", + "grass", + "czech", + "regularly", + "charges", + "tea", + "sudden", + "appeal", + "##ung", + "solution", + "describes", + "pierre", + "classification", + "glad", + "parking", + "##ning", + "belt", + "physics", + "99", + "rachel", + "add", + "hungarian", + "participate", + "expedition", + "damaged", + "gift", + "childhood", + "85", + "fifty", + "##red", + "mathematics", + "jumped", + "letting", + "defensive", + "mph", + "##ux", + "##gh", + "testing", + "##hip", + "hundreds", + "shoot", + "owners", + "matters", + "smoke", + "israeli", + "kentucky", + "dancing", + "mounted", + "grandfather", + "emma", + "designs", + "profit", + "argentina", + "##gs", + "truly", + "li", + "lawrence", + "cole", + "begun", + "detroit", + "willing", + "branches", + "smiling", + "decide", + "miami", + "enjoyed", + "recordings", + "##dale", + "poverty", + "ethnic", + "gay", + "##bi", + "gary", + "arabic", + "09", + "accompanied", + "##one", + "##ons", + "fishing", + "determine", + "residential", + "acid", + "##ary", + "alice", + "returns", + "starred", + "mail", + "##ang", + "jonathan", + "strategy", + "##ue", + "net", + "forty", + "cook", + "businesses", + "equivalent", + "commonwealth", + "distinct", + "ill", + "##cy", + "seriously", + "##ors", + "##ped", + "shift", + "harris", + "replace", + "rio", + "imagine", + "formula", + "ensure", + "##ber", + "additionally", + "scheme", + "conservation", + "occasionally", + "purposes", + "feels", + "favor", + "##and", + "##ore", + "1930s", + "contrast", + "hanging", + "hunt", + "movies", + "1904", + "instruments", + "victims", + "danish", + "christopher", + "busy", + "demon", + "sugar", + "earliest", + "colony", + "studying", + "balance", + "duties", + "##ks", + "belgium", + "slipped", + "carter", + "05", + "visible", + "stages", + "iraq", + "fifa", + "##im", + "commune", + "forming", + "zero", + "07", + "continuing", + "talked", + "counties", + "legend", + "bathroom", + "option", + "tail", + "clay", + "daughters", + "afterwards", + "severe", + "jaw", + "visitors", + "##ded", + "devices", + "aviation", + "russell", + "kate", + "##vi", + "entering", + "subjects", + "##ino", + "temporary", + "swimming", + "forth", + "smooth", + "ghost", + "audio", + "bush", + "operates", + "rocks", + "movements", + "signs", + "eddie", + "##tz", + "ann", + "voices", + "honorary", + "06", + "memories", + "dallas", + "pure", + "measures", + "racial", + "promised", + "66", + "harvard", + "ceo", + "16th", + "parliamentary", + "indicate", + "benefit", + "flesh", + "dublin", + "louisiana", + "1902", + "1901", + "patient", + "sleeping", + "1903", + "membership", + "coastal", + "medieval", + "wanting", + "element", + "scholars", + "rice", + "62", + "limit", + "survive", + "makeup", + "rating", + "definitely", + "collaboration", + "obvious", + "##tan", + "boss", + "ms", + "baron", + "birthday", + "linked", + "soil", + "diocese", + "##lan", + "ncaa", + "##mann", + "offensive", + "shell", + "shouldn", + "waist", + "##tus", + "plain", + "ross", + "organ", + "resolution", + "manufacturing", + "adding", + "relative", + "kennedy", + "98", + "whilst", + "moth", + "marketing", + "gardens", + "crash", + "72", + "heading", + "partners", + "credited", + "carlos", + "moves", + "cable", + "##zi", + "marshall", + "##out", + "depending", + "bottle", + "represents", + "rejected", + "responded", + "existed", + "04", + "jobs", + "denmark", + "lock", + "##ating", + "treated", + "graham", + "routes", + "talent", + "commissioner", + "drugs", + "secure", + "tests", + "reign", + "restored", + "photography", + "##gi", + "contributions", + "oklahoma", + "designer", + "disc", + "grin", + "seattle", + "robin", + "paused", + "atlanta", + "unusual", + "##gate", + "praised", + "las", + "laughing", + "satellite", + "hungary", + "visiting", + "##sky", + "interesting", + "factors", + "deck", + "poems", + "norman", + "##water", + "stuck", + "speaker", + "rifle", + "domain", + "premiered", + "##her", + "dc", + "comics", + "actors", + "01", + "reputation", + "eliminated", + "8th", + "ceiling", + "prisoners", + "script", + "##nce", + "leather", + "austin", + "mississippi", + "rapidly", + "admiral", + "parallel", + "charlotte", + "guilty", + "tools", + "gender", + "divisions", + "fruit", + "##bs", + "laboratory", + "nelson", + "fantasy", + "marry", + "rapid", + "aunt", + "tribe", + "requirements", + "aspects", + "suicide", + "amongst", + "adams", + "bone", + "ukraine", + "abc", + "kick", + "sees", + "edinburgh", + "clothing", + "column", + "rough", + "gods", + "hunting", + "broadway", + "gathered", + "concerns", + "##ek", + "spending", + "ty", + "12th", + "snapped", + "requires", + "solar", + "bones", + "cavalry", + "##tta", + "iowa", + "drinking", + "waste", + "index", + "franklin", + "charity", + "thompson", + "stewart", + "tip", + "flash", + "landscape", + "friday", + "enjoy", + "singh", + "poem", + "listening", + "##back", + "eighth", + "fred", + "differences", + "adapted", + "bomb", + "ukrainian", + "surgery", + "corporate", + "masters", + "anywhere", + "##more", + "waves", + "odd", + "sean", + "portugal", + "orleans", + "dick", + "debate", + "kent", + "eating", + "puerto", + "cleared", + "96", + "expect", + "cinema", + "97", + "guitarist", + "blocks", + "electrical", + "agree", + "involving", + "depth", + "dying", + "panel", + "struggle", + "##ged", + "peninsula", + "adults", + "novels", + "emerged", + "vienna", + "metro", + "debuted", + "shoes", + "tamil", + "songwriter", + "meets", + "prove", + "beating", + "instance", + "heaven", + "scared", + "sending", + "marks", + "artistic", + "passage", + "superior", + "03", + "significantly", + "shopping", + "##tive", + "retained", + "##izing", + "malaysia", + "technique", + "cheeks", + "##ola", + "warren", + "maintenance", + "destroy", + "extreme", + "allied", + "120", + "appearing", + "##yn", + "fill", + "advice", + "alabama", + "qualifying", + "policies", + "cleveland", + "hat", + "battery", + "smart", + "authors", + "10th", + "soundtrack", + "acted", + "dated", + "lb", + "glance", + "equipped", + "coalition", + "funny", + "outer", + "ambassador", + "roy", + "possibility", + "couples", + "campbell", + "dna", + "loose", + "ethan", + "supplies", + "1898", + "gonna", + "88", + "monster", + "##res", + "shake", + "agents", + "frequency", + "springs", + "dogs", + "practices", + "61", + "gang", + "plastic", + "easier", + "suggests", + "gulf", + "blade", + "exposed", + "colors", + "industries", + "markets", + "pan", + "nervous", + "electoral", + "charts", + "legislation", + "ownership", + "##idae", + "mac", + "appointment", + "shield", + "copy", + "assault", + "socialist", + "abbey", + "monument", + "license", + "throne", + "employment", + "jay", + "93", + "replacement", + "charter", + "cloud", + "powered", + "suffering", + "accounts", + "oak", + "connecticut", + "strongly", + "wright", + "colour", + "crystal", + "13th", + "context", + "welsh", + "networks", + "voiced", + "gabriel", + "jerry", + "##cing", + "forehead", + "mp", + "##ens", + "manage", + "schedule", + "totally", + "remix", + "##ii", + "forests", + "occupation", + "print", + "nicholas", + "brazilian", + "strategic", + "vampires", + "engineers", + "76", + "roots", + "seek", + "correct", + "instrumental", + "und", + "alfred", + "backed", + "hop", + "##des", + "stanley", + "robinson", + "traveled", + "wayne", + "welcome", + "austrian", + "achieve", + "67", + "exit", + "rates", + "1899", + "strip", + "whereas", + "##cs", + "sing", + "deeply", + "adventure", + "bobby", + "rick", + "jamie", + "careful", + "components", + "cap", + "useful", + "personality", + "knee", + "##shi", + "pushing", + "hosts", + "02", + "protest", + "ca", + "ottoman", + "symphony", + "##sis", + "63", + "boundary", + "1890", + "processes", + "considering", + "considerable", + "tons", + "##work", + "##ft", + "##nia", + "cooper", + "trading", + "dear", + "conduct", + "91", + "illegal", + "apple", + "revolutionary", + "holiday", + "definition", + "harder", + "##van", + "jacob", + "circumstances", + "destruction", + "##lle", + "popularity", + "grip", + "classified", + "liverpool", + "donald", + "baltimore", + "flows", + "seeking", + "honour", + "approval", + "92", + "mechanical", + "till", + "happening", + "statue", + "critic", + "increasingly", + "immediate", + "describe", + "commerce", + "stare", + "##ster", + "indonesia", + "meat", + "rounds", + "boats", + "baker", + "orthodox", + "depression", + "formally", + "worn", + "naked", + "claire", + "muttered", + "sentence", + "11th", + "emily", + "document", + "77", + "criticism", + "wished", + "vessel", + "spiritual", + "bent", + "virgin", + "parker", + "minimum", + "murray", + "lunch", + "danny", + "printed", + "compilation", + "keyboards", + "false", + "blow", + "belonged", + "68", + "raising", + "78", + "cutting", + "##board", + "pittsburgh", + "##up", + "9th", + "shadows", + "81", + "hated", + "indigenous", + "jon", + "15th", + "barry", + "scholar", + "ah", + "##zer", + "oliver", + "##gy", + "stick", + "susan", + "meetings", + "attracted", + "spell", + "romantic", + "##ver", + "ye", + "1895", + "photo", + "demanded", + "customers", + "##ac", + "1896", + "logan", + "revival", + "keys", + "modified", + "commanded", + "jeans", + "##ious", + "upset", + "raw", + "phil", + "detective", + "hiding", + "resident", + "vincent", + "##bly", + "experiences", + "diamond", + "defeating", + "coverage", + "lucas", + "external", + "parks", + "franchise", + "helen", + "bible", + "successor", + "percussion", + "celebrated", + "il", + "lift", + "profile", + "clan", + "romania", + "##ied", + "mills", + "##su", + "nobody", + "achievement", + "shrugged", + "fault", + "1897", + "rhythm", + "initiative", + "breakfast", + "carbon", + "700", + "69", + "lasted", + "violent", + "74", + "wound", + "ken", + "killer", + "gradually", + "filmed", + "\u00b0c", + "dollars", + "processing", + "94", + "remove", + "criticized", + "guests", + "sang", + "chemistry", + "##vin", + "legislature", + "disney", + "##bridge", + "uniform", + "escaped", + "integrated", + "proposal", + "purple", + "denied", + "liquid", + "karl", + "influential", + "morris", + "nights", + "stones", + "intense", + "experimental", + "twisted", + "71", + "84", + "##ld", + "pace", + "nazi", + "mitchell", + "ny", + "blind", + "reporter", + "newspapers", + "14th", + "centers", + "burn", + "basin", + "forgotten", + "surviving", + "filed", + "collections", + "monastery", + "losses", + "manual", + "couch", + "description", + "appropriate", + "merely", + "tag", + "missions", + "sebastian", + "restoration", + "replacing", + "triple", + "73", + "elder", + "julia", + "warriors", + "benjamin", + "julian", + "convinced", + "stronger", + "amazing", + "declined", + "versus", + "merchant", + "happens", + "output", + "finland", + "bare", + "barbara", + "absence", + "ignored", + "dawn", + "injuries", + "##port", + "producers", + "##ram", + "82", + "luis", + "##ities", + "kw", + "admit", + "expensive", + "electricity", + "nba", + "exception", + "symbol", + "##ving", + "ladies", + "shower", + "sheriff", + "characteristics", + "##je", + "aimed", + "button", + "ratio", + "effectively", + "summit", + "angle", + "jury", + "bears", + "foster", + "vessels", + "pants", + "executed", + "evans", + "dozen", + "advertising", + "kicked", + "patrol", + "1889", + "competitions", + "lifetime", + "principles", + "athletics", + "##logy", + "birmingham", + "sponsored", + "89", + "rob", + "nomination", + "1893", + "acoustic", + "##sm", + "creature", + "longest", + "##tra", + "credits", + "harbor", + "dust", + "josh", + "##so", + "territories", + "milk", + "infrastructure", + "completion", + "thailand", + "indians", + "leon", + "archbishop", + "##sy", + "assist", + "pitch", + "blake", + "arrangement", + "girlfriend", + "serbian", + "operational", + "hence", + "sad", + "scent", + "fur", + "dj", + "sessions", + "hp", + "refer", + "rarely", + "##ora", + "exists", + "1892", + "##ten", + "scientists", + "dirty", + "penalty", + "burst", + "portrait", + "seed", + "79", + "pole", + "limits", + "rival", + "1894", + "stable", + "alpha", + "grave", + "constitutional", + "alcohol", + "arrest", + "flower", + "mystery", + "devil", + "architectural", + "relationships", + "greatly", + "habitat", + "##istic", + "larry", + "progressive", + "remote", + "cotton", + "##ics", + "##ok", + "preserved", + "reaches", + "##ming", + "cited", + "86", + "vast", + "scholarship", + "decisions", + "cbs", + "joy", + "teach", + "1885", + "editions", + "knocked", + "eve", + "searching", + "partly", + "participation", + "gap", + "animated", + "fate", + "excellent", + "##ett", + "na", + "87", + "alternate", + "saints", + "youngest", + "##ily", + "climbed", + "##ita", + "##tors", + "suggest", + "##ct", + "discussion", + "staying", + "choir", + "lakes", + "jacket", + "revenue", + "nevertheless", + "peaked", + "instrument", + "wondering", + "annually", + "managing", + "neil", + "1891", + "signing", + "terry", + "##ice", + "apply", + "clinical", + "brooklyn", + "aim", + "catherine", + "fuck", + "farmers", + "figured", + "ninth", + "pride", + "hugh", + "evolution", + "ordinary", + "involvement", + "comfortable", + "shouted", + "tech", + "encouraged", + "taiwan", + "representation", + "sharing", + "##lia", + "##em", + "panic", + "exact", + "cargo", + "competing", + "fat", + "cried", + "83", + "1920s", + "occasions", + "pa", + "cabin", + "borders", + "utah", + "marcus", + "##isation", + "badly", + "muscles", + "##ance", + "victorian", + "transition", + "warner", + "bet", + "permission", + "##rin", + "slave", + "terrible", + "similarly", + "shares", + "seth", + "uefa", + "possession", + "medals", + "benefits", + "colleges", + "lowered", + "perfectly", + "mall", + "transit", + "##ye", + "##kar", + "publisher", + "##ened", + "harrison", + "deaths", + "elevation", + "##ae", + "asleep", + "machines", + "sigh", + "ash", + "hardly", + "argument", + "occasion", + "parent", + "leo", + "decline", + "1888", + "contribution", + "##ua", + "concentration", + "1000", + "opportunities", + "hispanic", + "guardian", + "extent", + "emotions", + "hips", + "mason", + "volumes", + "bloody", + "controversy", + "diameter", + "steady", + "mistake", + "phoenix", + "identify", + "violin", + "##sk", + "departure", + "richmond", + "spin", + "funeral", + "enemies", + "1864", + "gear", + "literally", + "connor", + "random", + "sergeant", + "grab", + "confusion", + "1865", + "transmission", + "informed", + "op", + "leaning", + "sacred", + "suspended", + "thinks", + "gates", + "portland", + "luck", + "agencies", + "yours", + "hull", + "expert", + "muscle", + "layer", + "practical", + "sculpture", + "jerusalem", + "latest", + "lloyd", + "statistics", + "deeper", + "recommended", + "warrior", + "arkansas", + "mess", + "supports", + "greg", + "eagle", + "1880", + "recovered", + "rated", + "concerts", + "rushed", + "##ano", + "stops", + "eggs", + "files", + "premiere", + "keith", + "##vo", + "delhi", + "turner", + "pit", + "affair", + "belief", + "paint", + "##zing", + "mate", + "##ach", + "##ev", + "victim", + "##ology", + "withdrew", + "bonus", + "styles", + "fled", + "##ud", + "glasgow", + "technologies", + "funded", + "nbc", + "adaptation", + "##ata", + "portrayed", + "cooperation", + "supporters", + "judges", + "bernard", + "justin", + "hallway", + "ralph", + "##ick", + "graduating", + "controversial", + "distant", + "continental", + "spider", + "bite", + "##ho", + "recognize", + "intention", + "mixing", + "##ese", + "egyptian", + "bow", + "tourism", + "suppose", + "claiming", + "tiger", + "dominated", + "participants", + "vi", + "##ru", + "nurse", + "partially", + "tape", + "##rum", + "psychology", + "##rn", + "essential", + "touring", + "duo", + "voting", + "civilian", + "emotional", + "channels", + "##king", + "apparent", + "hebrew", + "1887", + "tommy", + "carrier", + "intersection", + "beast", + "hudson", + "##gar", + "##zo", + "lab", + "nova", + "bench", + "discuss", + "costa", + "##ered", + "detailed", + "behalf", + "drivers", + "unfortunately", + "obtain", + "##lis", + "rocky", + "##dae", + "siege", + "friendship", + "honey", + "##rian", + "1861", + "amy", + "hang", + "posted", + "governments", + "collins", + "respond", + "wildlife", + "preferred", + "operator", + "##po", + "laura", + "pregnant", + "videos", + "dennis", + "suspected", + "boots", + "instantly", + "weird", + "automatic", + "businessman", + "alleged", + "placing", + "throwing", + "ph", + "mood", + "1862", + "perry", + "venue", + "jet", + "remainder", + "##lli", + "##ci", + "passion", + "biological", + "boyfriend", + "1863", + "dirt", + "buffalo", + "ron", + "segment", + "fa", + "abuse", + "##era", + "genre", + "thrown", + "stroke", + "colored", + "stress", + "exercise", + "displayed", + "##gen", + "struggled", + "##tti", + "abroad", + "dramatic", + "wonderful", + "thereafter", + "madrid", + "component", + "widespread", + "##sed", + "tale", + "citizen", + "todd", + "monday", + "1886", + "vancouver", + "overseas", + "forcing", + "crying", + "descent", + "##ris", + "discussed", + "substantial", + "ranks", + "regime", + "1870", + "provinces", + "switch", + "drum", + "zane", + "ted", + "tribes", + "proof", + "lp", + "cream", + "researchers", + "volunteer", + "manor", + "silk", + "milan", + "donated", + "allies", + "venture", + "principle", + "delivery", + "enterprise", + "##ves", + "##ans", + "bars", + "traditionally", + "witch", + "reminded", + "copper", + "##uk", + "pete", + "inter", + "links", + "colin", + "grinned", + "elsewhere", + "competitive", + "frequent", + "##oy", + "scream", + "##hu", + "tension", + "texts", + "submarine", + "finnish", + "defending", + "defend", + "pat", + "detail", + "1884", + "affiliated", + "stuart", + "themes", + "villa", + "periods", + "tool", + "belgian", + "ruling", + "crimes", + "answers", + "folded", + "licensed", + "resort", + "demolished", + "hans", + "lucy", + "1881", + "lion", + "traded", + "photographs", + "writes", + "craig", + "##fa", + "trials", + "generated", + "beth", + "noble", + "debt", + "percentage", + "yorkshire", + "erected", + "ss", + "viewed", + "grades", + "confidence", + "ceased", + "islam", + "telephone", + "retail", + "##ible", + "chile", + "m\u00b2", + "roberts", + "sixteen", + "##ich", + "commented", + "hampshire", + "innocent", + "dual", + "pounds", + "checked", + "regulations", + "afghanistan", + "sung", + "rico", + "liberty", + "assets", + "bigger", + "options", + "angels", + "relegated", + "tribute", + "wells", + "attending", + "leaf", + "##yan", + "butler", + "romanian", + "forum", + "monthly", + "lisa", + "patterns", + "gmina", + "##tory", + "madison", + "hurricane", + "rev", + "##ians", + "bristol", + "##ula", + "elite", + "valuable", + "disaster", + "democracy", + "awareness", + "germans", + "freyja", + "##ins", + "loop", + "absolutely", + "paying", + "populations", + "maine", + "sole", + "prayer", + "spencer", + "releases", + "doorway", + "bull", + "##ani", + "lover", + "midnight", + "conclusion", + "##sson", + "thirteen", + "lily", + "mediterranean", + "##lt", + "nhl", + "proud", + "sample", + "##hill", + "drummer", + "guinea", + "##ova", + "murphy", + "climb", + "##ston", + "instant", + "attributed", + "horn", + "ain", + "railways", + "steven", + "##ao", + "autumn", + "ferry", + "opponent", + "root", + "traveling", + "secured", + "corridor", + "stretched", + "tales", + "sheet", + "trinity", + "cattle", + "helps", + "indicates", + "manhattan", + "murdered", + "fitted", + "1882", + "gentle", + "grandmother", + "mines", + "shocked", + "vegas", + "produces", + "##light", + "caribbean", + "##ou", + "belong", + "continuous", + "desperate", + "drunk", + "historically", + "trio", + "waved", + "raf", + "dealing", + "nathan", + "bat", + "murmured", + "interrupted", + "residing", + "scientist", + "pioneer", + "harold", + "aaron", + "##net", + "delta", + "attempting", + "minority", + "mini", + "believes", + "chorus", + "tend", + "lots", + "eyed", + "indoor", + "load", + "shots", + "updated", + "jail", + "##llo", + "concerning", + "connecting", + "wealth", + "##ved", + "slaves", + "arrive", + "rangers", + "sufficient", + "rebuilt", + "##wick", + "cardinal", + "flood", + "muhammad", + "whenever", + "relation", + "runners", + "moral", + "repair", + "viewers", + "arriving", + "revenge", + "punk", + "assisted", + "bath", + "fairly", + "breathe", + "lists", + "innings", + "illustrated", + "whisper", + "nearest", + "voters", + "clinton", + "ties", + "ultimate", + "screamed", + "beijing", + "lions", + "andre", + "fictional", + "gathering", + "comfort", + "radar", + "suitable", + "dismissed", + "hms", + "ban", + "pine", + "wrist", + "atmosphere", + "voivodeship", + "bid", + "timber", + "##ned", + "##nan", + "giants", + "##ane", + "cameron", + "recovery", + "uss", + "identical", + "categories", + "switched", + "serbia", + "laughter", + "noah", + "ensemble", + "therapy", + "peoples", + "touching", + "##off", + "locally", + "pearl", + "platforms", + "everywhere", + "ballet", + "tables", + "lanka", + "herbert", + "outdoor", + "toured", + "derek", + "1883", + "spaces", + "contested", + "swept", + "1878", + "exclusive", + "slight", + "connections", + "##dra", + "winds", + "prisoner", + "collective", + "bangladesh", + "tube", + "publicly", + "wealthy", + "thai", + "##ys", + "isolated", + "select", + "##ric", + "insisted", + "pen", + "fortune", + "ticket", + "spotted", + "reportedly", + "animation", + "enforcement", + "tanks", + "110", + "decides", + "wider", + "lowest", + "owen", + "##time", + "nod", + "hitting", + "##hn", + "gregory", + "furthermore", + "magazines", + "fighters", + "solutions", + "##ery", + "pointing", + "requested", + "peru", + "reed", + "chancellor", + "knights", + "mask", + "worker", + "eldest", + "flames", + "reduction", + "1860", + "volunteers", + "##tis", + "reporting", + "##hl", + "wire", + "advisory", + "endemic", + "origins", + "settlers", + "pursue", + "knock", + "consumer", + "1876", + "eu", + "compound", + "creatures", + "mansion", + "sentenced", + "ivan", + "deployed", + "guitars", + "frowned", + "involves", + "mechanism", + "kilometers", + "perspective", + "shops", + "maps", + "terminus", + "duncan", + "alien", + "fist", + "bridges", + "##pers", + "heroes", + "fed", + "derby", + "swallowed", + "##ros", + "patent", + "sara", + "illness", + "characterized", + "adventures", + "slide", + "hawaii", + "jurisdiction", + "##op", + "organised", + "##side", + "adelaide", + "walks", + "biology", + "se", + "##ties", + "rogers", + "swing", + "tightly", + "boundaries", + "##rie", + "prepare", + "implementation", + "stolen", + "##sha", + "certified", + "colombia", + "edwards", + "garage", + "##mm", + "recalled", + "##ball", + "rage", + "harm", + "nigeria", + "breast", + "##ren", + "furniture", + "pupils", + "settle", + "##lus", + "cuba", + "balls", + "client", + "alaska", + "21st", + "linear", + "thrust", + "celebration", + "latino", + "genetic", + "terror", + "##cia", + "##ening", + "lightning", + "fee", + "witness", + "lodge", + "establishing", + "skull", + "##ique", + "earning", + "hood", + "##ei", + "rebellion", + "wang", + "sporting", + "warned", + "missile", + "devoted", + "activist", + "porch", + "worship", + "fourteen", + "package", + "1871", + "decorated", + "##shire", + "housed", + "##ock", + "chess", + "sailed", + "doctors", + "oscar", + "joan", + "treat", + "garcia", + "harbour", + "jeremy", + "##ire", + "traditions", + "dominant", + "jacques", + "##gon", + "##wan", + "relocated", + "1879", + "amendment", + "sized", + "companion", + "simultaneously", + "volleyball", + "spun", + "acre", + "increases", + "stopping", + "loves", + "belongs", + "affect", + "drafted", + "tossed", + "scout", + "battles", + "1875", + "filming", + "shoved", + "munich", + "tenure", + "vertical", + "romance", + "pc", + "##cher", + "argue", + "##ical", + "craft", + "ranging", + "www", + "opens", + "honest", + "tyler", + "yesterday", + "virtual", + "##let", + "muslims", + "reveal", + "snake", + "immigrants", + "radical", + "screaming", + "speakers", + "firing", + "saving", + "belonging", + "ease", + "lighting", + "prefecture", + "blame", + "farmer", + "hungry", + "grows", + "rubbed", + "beam", + "sur", + "subsidiary", + "##cha", + "armenian", + "sao", + "dropping", + "conventional", + "##fer", + "microsoft", + "reply", + "qualify", + "spots", + "1867", + "sweat", + "festivals", + "##ken", + "immigration", + "physician", + "discover", + "exposure", + "sandy", + "explanation", + "isaac", + "implemented", + "##fish", + "hart", + "initiated", + "connect", + "stakes", + "presents", + "heights", + "householder", + "pleased", + "tourist", + "regardless", + "slip", + "closest", + "##ction", + "surely", + "sultan", + "brings", + "riley", + "preparation", + "aboard", + "slammed", + "baptist", + "experiment", + "ongoing", + "interstate", + "organic", + "playoffs", + "##ika", + "1877", + "130", + "##tar", + "hindu", + "error", + "tours", + "tier", + "plenty", + "arrangements", + "talks", + "trapped", + "excited", + "sank", + "ho", + "athens", + "1872", + "denver", + "welfare", + "suburb", + "athletes", + "trick", + "diverse", + "belly", + "exclusively", + "yelled", + "1868", + "##med", + "conversion", + "##ette", + "1874", + "internationally", + "computers", + "conductor", + "abilities", + "sensitive", + "hello", + "dispute", + "measured", + "globe", + "rocket", + "prices", + "amsterdam", + "flights", + "tigers", + "inn", + "municipalities", + "emotion", + "references", + "3d", + "##mus", + "explains", + "airlines", + "manufactured", + "pm", + "archaeological", + "1873", + "interpretation", + "devon", + "comment", + "##ites", + "settlements", + "kissing", + "absolute", + "improvement", + "suite", + "impressed", + "barcelona", + "sullivan", + "jefferson", + "towers", + "jesse", + "julie", + "##tin", + "##lu", + "grandson", + "hi", + "gauge", + "regard", + "rings", + "interviews", + "trace", + "raymond", + "thumb", + "departments", + "burns", + "serial", + "bulgarian", + "scores", + "demonstrated", + "##ix", + "1866", + "kyle", + "alberta", + "underneath", + "romanized", + "##ward", + "relieved", + "acquisition", + "phrase", + "cliff", + "reveals", + "han", + "cuts", + "merger", + "custom", + "##dar", + "nee", + "gilbert", + "graduation", + "##nts", + "assessment", + "cafe", + "difficulty", + "demands", + "swung", + "democrat", + "jennifer", + "commons", + "1940s", + "grove", + "##yo", + "completing", + "focuses", + "sum", + "substitute", + "bearing", + "stretch", + "reception", + "##py", + "reflected", + "essentially", + "destination", + "pairs", + "##ched", + "survival", + "resource", + "##bach", + "promoting", + "doubles", + "messages", + "tear", + "##down", + "##fully", + "parade", + "florence", + "harvey", + "incumbent", + "partial", + "framework", + "900", + "pedro", + "frozen", + "procedure", + "olivia", + "controls", + "##mic", + "shelter", + "personally", + "temperatures", + "##od", + "brisbane", + "tested", + "sits", + "marble", + "comprehensive", + "oxygen", + "leonard", + "##kov", + "inaugural", + "iranian", + "referring", + "quarters", + "attitude", + "##ivity", + "mainstream", + "lined", + "mars", + "dakota", + "norfolk", + "unsuccessful", + "##\u00b0", + "explosion", + "helicopter", + "congressional", + "##sing", + "inspector", + "bitch", + "seal", + "departed", + "divine", + "##ters", + "coaching", + "examination", + "punishment", + "manufacturer", + "sink", + "columns", + "unincorporated", + "signals", + "nevada", + "squeezed", + "dylan", + "dining", + "photos", + "martial", + "manuel", + "eighteen", + "elevator", + "brushed", + "plates", + "ministers", + "ivy", + "congregation", + "##len", + "slept", + "specialized", + "taxes", + "curve", + "restricted", + "negotiations", + "likes", + "statistical", + "arnold", + "inspiration", + "execution", + "bold", + "intermediate", + "significance", + "margin", + "ruler", + "wheels", + "gothic", + "intellectual", + "dependent", + "listened", + "eligible", + "buses", + "widow", + "syria", + "earn", + "cincinnati", + "collapsed", + "recipient", + "secrets", + "accessible", + "philippine", + "maritime", + "goddess", + "clerk", + "surrender", + "breaks", + "playoff", + "database", + "##ified", + "##lon", + "ideal", + "beetle", + "aspect", + "soap", + "regulation", + "strings", + "expand", + "anglo", + "shorter", + "crosses", + "retreat", + "tough", + "coins", + "wallace", + "directions", + "pressing", + "##oon", + "shipping", + "locomotives", + "comparison", + "topics", + "nephew", + "##mes", + "distinction", + "honors", + "travelled", + "sierra", + "ibn", + "##over", + "fortress", + "sa", + "recognised", + "carved", + "1869", + "clients", + "##dan", + "intent", + "##mar", + "coaches", + "describing", + "bread", + "##ington", + "beaten", + "northwestern", + "##ona", + "merit", + "youtube", + "collapse", + "challenges", + "em", + "historians", + "objective", + "submitted", + "virus", + "attacking", + "drake", + "assume", + "##ere", + "diseases", + "marc", + "stem", + "leeds", + "##cus", + "##ab", + "farming", + "glasses", + "##lock", + "visits", + "nowhere", + "fellowship", + "relevant", + "carries", + "restaurants", + "experiments", + "101", + "constantly", + "bases", + "targets", + "shah", + "tenth", + "opponents", + "verse", + "territorial", + "##ira", + "writings", + "corruption", + "##hs", + "instruction", + "inherited", + "reverse", + "emphasis", + "##vic", + "employee", + "arch", + "keeps", + "rabbi", + "watson", + "payment", + "uh", + "##ala", + "nancy", + "##tre", + "venice", + "fastest", + "sexy", + "banned", + "adrian", + "properly", + "ruth", + "touchdown", + "dollar", + "boards", + "metre", + "circles", + "edges", + "favour", + "comments", + "ok", + "travels", + "liberation", + "scattered", + "firmly", + "##ular", + "holland", + "permitted", + "diesel", + "kenya", + "den", + "originated", + "##ral", + "demons", + "resumed", + "dragged", + "rider", + "##rus", + "servant", + "blinked", + "extend", + "torn", + "##ias", + "##sey", + "input", + "meal", + "everybody", + "cylinder", + "kinds", + "camps", + "##fe", + "bullet", + "logic", + "##wn", + "croatian", + "evolved", + "healthy", + "fool", + "chocolate", + "wise", + "preserve", + "pradesh", + "##ess", + "respective", + "1850", + "##ew", + "chicken", + "artificial", + "gross", + "corresponding", + "convicted", + "cage", + "caroline", + "dialogue", + "##dor", + "narrative", + "stranger", + "mario", + "br", + "christianity", + "failing", + "trent", + "commanding", + "buddhist", + "1848", + "maurice", + "focusing", + "yale", + "bike", + "altitude", + "##ering", + "mouse", + "revised", + "##sley", + "veteran", + "##ig", + "pulls", + "theology", + "crashed", + "campaigns", + "legion", + "##ability", + "drag", + "excellence", + "customer", + "cancelled", + "intensity", + "excuse", + "##lar", + "liga", + "participating", + "contributing", + "printing", + "##burn", + "variable", + "##rk", + "curious", + "bin", + "legacy", + "renaissance", + "##my", + "symptoms", + "binding", + "vocalist", + "dancer", + "##nie", + "grammar", + "gospel", + "democrats", + "ya", + "enters", + "sc", + "diplomatic", + "hitler", + "##ser", + "clouds", + "mathematical", + "quit", + "defended", + "oriented", + "##heim", + "fundamental", + "hardware", + "impressive", + "equally", + "convince", + "confederate", + "guilt", + "chuck", + "sliding", + "##ware", + "magnetic", + "narrowed", + "petersburg", + "bulgaria", + "otto", + "phd", + "skill", + "##ama", + "reader", + "hopes", + "pitcher", + "reservoir", + "hearts", + "automatically", + "expecting", + "mysterious", + "bennett", + "extensively", + "imagined", + "seeds", + "monitor", + "fix", + "##ative", + "journalism", + "struggling", + "signature", + "ranch", + "encounter", + "photographer", + "observation", + "protests", + "##pin", + "influences", + "##hr", + "calendar", + "##all", + "cruz", + "croatia", + "locomotive", + "hughes", + "naturally", + "shakespeare", + "basement", + "hook", + "uncredited", + "faded", + "theories", + "approaches", + "dare", + "phillips", + "filling", + "fury", + "obama", + "##ain", + "efficient", + "arc", + "deliver", + "min", + "raid", + "breeding", + "inducted", + "leagues", + "efficiency", + "axis", + "montana", + "eagles", + "##ked", + "supplied", + "instructions", + "karen", + "picking", + "indicating", + "trap", + "anchor", + "practically", + "christians", + "tomb", + "vary", + "occasional", + "electronics", + "lords", + "readers", + "newcastle", + "faint", + "innovation", + "collect", + "situations", + "engagement", + "160", + "claude", + "mixture", + "##feld", + "peer", + "tissue", + "logo", + "lean", + "##ration", + "\u00b0f", + "floors", + "##ven", + "architects", + "reducing", + "##our", + "##ments", + "rope", + "1859", + "ottawa", + "##har", + "samples", + "banking", + "declaration", + "proteins", + "resignation", + "francois", + "saudi", + "advocate", + "exhibited", + "armor", + "twins", + "divorce", + "##ras", + "abraham", + "reviewed", + "jo", + "temporarily", + "matrix", + "physically", + "pulse", + "curled", + "##ena", + "difficulties", + "bengal", + "usage", + "##ban", + "annie", + "riders", + "certificate", + "##pi", + "holes", + "warsaw", + "distinctive", + "jessica", + "##mon", + "mutual", + "1857", + "customs", + "circular", + "eugene", + "removal", + "loaded", + "mere", + "vulnerable", + "depicted", + "generations", + "dame", + "heir", + "enormous", + "lightly", + "climbing", + "pitched", + "lessons", + "pilots", + "nepal", + "ram", + "google", + "preparing", + "brad", + "louise", + "renowned", + "##\u2082", + "liam", + "##ably", + "plaza", + "shaw", + "sophie", + "brilliant", + "bills", + "##bar", + "##nik", + "fucking", + "mainland", + "server", + "pleasant", + "seized", + "veterans", + "jerked", + "fail", + "beta", + "brush", + "radiation", + "stored", + "warmth", + "southeastern", + "nate", + "sin", + "raced", + "berkeley", + "joke", + "athlete", + "designation", + "trunk", + "##low", + "roland", + "qualification", + "archives", + "heels", + "artwork", + "receives", + "judicial", + "reserves", + "##bed", + "woke", + "installation", + "abu", + "floating", + "fake", + "lesser", + "excitement", + "interface", + "concentrated", + "addressed", + "characteristic", + "amanda", + "saxophone", + "monk", + "auto", + "##bus", + "releasing", + "egg", + "dies", + "interaction", + "defender", + "ce", + "outbreak", + "glory", + "loving", + "##bert", + "sequel", + "consciousness", + "http", + "awake", + "ski", + "enrolled", + "##ress", + "handling", + "rookie", + "brow", + "somebody", + "biography", + "warfare", + "amounts", + "contracts", + "presentation", + "fabric", + "dissolved", + "challenged", + "meter", + "psychological", + "lt", + "elevated", + "rally", + "accurate", + "##tha", + "hospitals", + "undergraduate", + "specialist", + "venezuela", + "exhibit", + "shed", + "nursing", + "protestant", + "fluid", + "structural", + "footage", + "jared", + "consistent", + "prey", + "##ska", + "succession", + "reflect", + "exile", + "lebanon", + "wiped", + "suspect", + "shanghai", + "resting", + "integration", + "preservation", + "marvel", + "variant", + "pirates", + "sheep", + "rounded", + "capita", + "sailing", + "colonies", + "manuscript", + "deemed", + "variations", + "clarke", + "functional", + "emerging", + "boxing", + "relaxed", + "curse", + "azerbaijan", + "heavyweight", + "nickname", + "editorial", + "rang", + "grid", + "tightened", + "earthquake", + "flashed", + "miguel", + "rushing", + "##ches", + "improvements", + "boxes", + "brooks", + "180", + "consumption", + "molecular", + "felix", + "societies", + "repeatedly", + "variation", + "aids", + "civic", + "graphics", + "professionals", + "realm", + "autonomous", + "receiver", + "delayed", + "workshop", + "militia", + "chairs", + "trump", + "canyon", + "##point", + "harsh", + "extending", + "lovely", + "happiness", + "##jan", + "stake", + "eyebrows", + "embassy", + "wellington", + "hannah", + "##ella", + "sony", + "corners", + "bishops", + "swear", + "cloth", + "contents", + "xi", + "namely", + "commenced", + "1854", + "stanford", + "nashville", + "courage", + "graphic", + "commitment", + "garrison", + "##bin", + "hamlet", + "clearing", + "rebels", + "attraction", + "literacy", + "cooking", + "ruins", + "temples", + "jenny", + "humanity", + "celebrate", + "hasn", + "freight", + "sixty", + "rebel", + "bastard", + "##art", + "newton", + "##ada", + "deer", + "##ges", + "##ching", + "smiles", + "delaware", + "singers", + "##ets", + "approaching", + "assists", + "flame", + "##ph", + "boulevard", + "barrel", + "planted", + "##ome", + "pursuit", + "##sia", + "consequences", + "posts", + "shallow", + "invitation", + "rode", + "depot", + "ernest", + "kane", + "rod", + "concepts", + "preston", + "topic", + "chambers", + "striking", + "blast", + "arrives", + "descendants", + "montgomery", + "ranges", + "worlds", + "##lay", + "##ari", + "span", + "chaos", + "praise", + "##ag", + "fewer", + "1855", + "sanctuary", + "mud", + "fbi", + "##ions", + "programmes", + "maintaining", + "unity", + "harper", + "bore", + "handsome", + "closure", + "tournaments", + "thunder", + "nebraska", + "linda", + "facade", + "puts", + "satisfied", + "argentine", + "dale", + "cork", + "dome", + "panama", + "##yl", + "1858", + "tasks", + "experts", + "##ates", + "feeding", + "equation", + "##las", + "##ida", + "##tu", + "engage", + "bryan", + "##ax", + "um", + "quartet", + "melody", + "disbanded", + "sheffield", + "blocked", + "gasped", + "delay", + "kisses", + "maggie", + "connects", + "##non", + "sts", + "poured", + "creator", + "publishers", + "##we", + "guided", + "ellis", + "extinct", + "hug", + "gaining", + "##ord", + "complicated", + "##bility", + "poll", + "clenched", + "investigate", + "##use", + "thereby", + "quantum", + "spine", + "cdp", + "humor", + "kills", + "administered", + "semifinals", + "##du", + "encountered", + "ignore", + "##bu", + "commentary", + "##maker", + "bother", + "roosevelt", + "140", + "plains", + "halfway", + "flowing", + "cultures", + "crack", + "imprisoned", + "neighboring", + "airline", + "##ses", + "##view", + "##mate", + "##ec", + "gather", + "wolves", + "marathon", + "transformed", + "##ill", + "cruise", + "organisations", + "carol", + "punch", + "exhibitions", + "numbered", + "alarm", + "ratings", + "daddy", + "silently", + "##stein", + "queens", + "colours", + "impression", + "guidance", + "liu", + "tactical", + "##rat", + "marshal", + "della", + "arrow", + "##ings", + "rested", + "feared", + "tender", + "owns", + "bitter", + "advisor", + "escort", + "##ides", + "spare", + "farms", + "grants", + "##ene", + "dragons", + "encourage", + "colleagues", + "cameras", + "##und", + "sucked", + "pile", + "spirits", + "prague", + "statements", + "suspension", + "landmark", + "fence", + "torture", + "recreation", + "bags", + "permanently", + "survivors", + "pond", + "spy", + "predecessor", + "bombing", + "coup", + "##og", + "protecting", + "transformation", + "glow", + "##lands", + "##book", + "dug", + "priests", + "andrea", + "feat", + "barn", + "jumping", + "##chen", + "##ologist", + "##con", + "casualties", + "stern", + "auckland", + "pipe", + "serie", + "revealing", + "ba", + "##bel", + "trevor", + "mercy", + "spectrum", + "yang", + "consist", + "governing", + "collaborated", + "possessed", + "epic", + "comprises", + "blew", + "shane", + "##ack", + "lopez", + "honored", + "magical", + "sacrifice", + "judgment", + "perceived", + "hammer", + "mtv", + "baronet", + "tune", + "das", + "missionary", + "sheets", + "350", + "neutral", + "oral", + "threatening", + "attractive", + "shade", + "aims", + "seminary", + "##master", + "estates", + "1856", + "michel", + "wounds", + "refugees", + "manufacturers", + "##nic", + "mercury", + "syndrome", + "porter", + "##iya", + "##din", + "hamburg", + "identification", + "upstairs", + "purse", + "widened", + "pause", + "cared", + "breathed", + "affiliate", + "santiago", + "prevented", + "celtic", + "fisher", + "125", + "recruited", + "byzantine", + "reconstruction", + "farther", + "##mp", + "diet", + "sake", + "au", + "spite", + "sensation", + "##ert", + "blank", + "separation", + "105", + "##hon", + "vladimir", + "armies", + "anime", + "##lie", + "accommodate", + "orbit", + "cult", + "sofia", + "archive", + "##ify", + "##box", + "founders", + "sustained", + "disorder", + "honours", + "northeastern", + "mia", + "crops", + "violet", + "threats", + "blanket", + "fires", + "canton", + "followers", + "southwestern", + "prototype", + "voyage", + "assignment", + "altered", + "moderate", + "protocol", + "pistol", + "##eo", + "questioned", + "brass", + "lifting", + "1852", + "math", + "authored", + "##ual", + "doug", + "dimensional", + "dynamic", + "##san", + "1851", + "pronounced", + "grateful", + "quest", + "uncomfortable", + "boom", + "presidency", + "stevens", + "relating", + "politicians", + "chen", + "barrier", + "quinn", + "diana", + "mosque", + "tribal", + "cheese", + "palmer", + "portions", + "sometime", + "chester", + "treasure", + "wu", + "bend", + "download", + "millions", + "reforms", + "registration", + "##osa", + "consequently", + "monitoring", + "ate", + "preliminary", + "brandon", + "invented", + "ps", + "eaten", + "exterior", + "intervention", + "ports", + "documented", + "log", + "displays", + "lecture", + "sally", + "favourite", + "##itz", + "vermont", + "lo", + "invisible", + "isle", + "breed", + "##ator", + "journalists", + "relay", + "speaks", + "backward", + "explore", + "midfielder", + "actively", + "stefan", + "procedures", + "cannon", + "blond", + "kenneth", + "centered", + "servants", + "chains", + "libraries", + "malcolm", + "essex", + "henri", + "slavery", + "##hal", + "facts", + "fairy", + "coached", + "cassie", + "cats", + "washed", + "cop", + "##fi", + "announcement", + "item", + "2000s", + "vinyl", + "activated", + "marco", + "frontier", + "growled", + "curriculum", + "##das", + "loyal", + "accomplished", + "leslie", + "ritual", + "kenny", + "##00", + "vii", + "napoleon", + "hollow", + "hybrid", + "jungle", + "stationed", + "friedrich", + "counted", + "##ulated", + "platinum", + "theatrical", + "seated", + "col", + "rubber", + "glen", + "1840", + "diversity", + "healing", + "extends", + "id", + "provisions", + "administrator", + "columbus", + "##oe", + "tributary", + "te", + "assured", + "org", + "##uous", + "prestigious", + "examined", + "lectures", + "grammy", + "ronald", + "associations", + "bailey", + "allan", + "essays", + "flute", + "believing", + "consultant", + "proceedings", + "travelling", + "1853", + "kit", + "kerala", + "yugoslavia", + "buddy", + "methodist", + "##ith", + "burial", + "centres", + "batman", + "##nda", + "discontinued", + "bo", + "dock", + "stockholm", + "lungs", + "severely", + "##nk", + "citing", + "manga", + "##ugh", + "steal", + "mumbai", + "iraqi", + "robot", + "celebrity", + "bride", + "broadcasts", + "abolished", + "pot", + "joel", + "overhead", + "franz", + "packed", + "reconnaissance", + "johann", + "acknowledged", + "introduce", + "handled", + "doctorate", + "developments", + "drinks", + "alley", + "palestine", + "##nis", + "##aki", + "proceeded", + "recover", + "bradley", + "grain", + "patch", + "afford", + "infection", + "nationalist", + "legendary", + "##ath", + "interchange", + "virtually", + "gen", + "gravity", + "exploration", + "amber", + "vital", + "wishes", + "powell", + "doctrine", + "elbow", + "screenplay", + "##bird", + "contribute", + "indonesian", + "pet", + "creates", + "##com", + "enzyme", + "kylie", + "discipline", + "drops", + "manila", + "hunger", + "##ien", + "layers", + "suffer", + "fever", + "bits", + "monica", + "keyboard", + "manages", + "##hood", + "searched", + "appeals", + "##bad", + "testament", + "grande", + "reid", + "##war", + "beliefs", + "congo", + "##ification", + "##dia", + "si", + "requiring", + "##via", + "casey", + "1849", + "regret", + "streak", + "rape", + "depends", + "syrian", + "sprint", + "pound", + "tourists", + "upcoming", + "pub", + "##xi", + "tense", + "##els", + "practiced", + "echo", + "nationwide", + "guild", + "motorcycle", + "liz", + "##zar", + "chiefs", + "desired", + "elena", + "bye", + "precious", + "absorbed", + "relatives", + "booth", + "pianist", + "##mal", + "citizenship", + "exhausted", + "wilhelm", + "##ceae", + "##hed", + "noting", + "quarterback", + "urge", + "hectares", + "##gue", + "ace", + "holly", + "##tal", + "blonde", + "davies", + "parked", + "sustainable", + "stepping", + "twentieth", + "airfield", + "galaxy", + "nest", + "chip", + "##nell", + "tan", + "shaft", + "paulo", + "requirement", + "##zy", + "paradise", + "tobacco", + "trans", + "renewed", + "vietnamese", + "##cker", + "##ju", + "suggesting", + "catching", + "holmes", + "enjoying", + "md", + "trips", + "colt", + "holder", + "butterfly", + "nerve", + "reformed", + "cherry", + "bowling", + "trailer", + "carriage", + "goodbye", + "appreciate", + "toy", + "joshua", + "interactive", + "enabled", + "involve", + "##kan", + "collar", + "determination", + "bunch", + "facebook", + "recall", + "shorts", + "superintendent", + "episcopal", + "frustration", + "giovanni", + "nineteenth", + "laser", + "privately", + "array", + "circulation", + "##ovic", + "armstrong", + "deals", + "painful", + "permit", + "discrimination", + "##wi", + "aires", + "retiring", + "cottage", + "ni", + "##sta", + "horizon", + "ellen", + "jamaica", + "ripped", + "fernando", + "chapters", + "playstation", + "patron", + "lecturer", + "navigation", + "behaviour", + "genes", + "georgian", + "export", + "solomon", + "rivals", + "swift", + "seventeen", + "rodriguez", + "princeton", + "independently", + "sox", + "1847", + "arguing", + "entity", + "casting", + "hank", + "criteria", + "oakland", + "geographic", + "milwaukee", + "reflection", + "expanding", + "conquest", + "dubbed", + "##tv", + "halt", + "brave", + "brunswick", + "doi", + "arched", + "curtis", + "divorced", + "predominantly", + "somerset", + "streams", + "ugly", + "zoo", + "horrible", + "curved", + "buenos", + "fierce", + "dictionary", + "vector", + "theological", + "unions", + "handful", + "stability", + "chan", + "punjab", + "segments", + "##lly", + "altar", + "ignoring", + "gesture", + "monsters", + "pastor", + "##stone", + "thighs", + "unexpected", + "operators", + "abruptly", + "coin", + "compiled", + "associates", + "improving", + "migration", + "pin", + "##ose", + "compact", + "collegiate", + "reserved", + "##urs", + "quarterfinals", + "roster", + "restore", + "assembled", + "hurry", + "oval", + "##cies", + "1846", + "flags", + "martha", + "##del", + "victories", + "sharply", + "##rated", + "argues", + "deadly", + "neo", + "drawings", + "symbols", + "performer", + "##iel", + "griffin", + "restrictions", + "editing", + "andrews", + "java", + "journals", + "arabia", + "compositions", + "dee", + "pierce", + "removing", + "hindi", + "casino", + "runway", + "civilians", + "minds", + "nasa", + "hotels", + "##zation", + "refuge", + "rent", + "retain", + "potentially", + "conferences", + "suburban", + "conducting", + "##tto", + "##tions", + "##tle", + "descended", + "massacre", + "##cal", + "ammunition", + "terrain", + "fork", + "souls", + "counts", + "chelsea", + "durham", + "drives", + "cab", + "##bank", + "perth", + "realizing", + "palestinian", + "finn", + "simpson", + "##dal", + "betty", + "##ule", + "moreover", + "particles", + "cardinals", + "tent", + "evaluation", + "extraordinary", + "##oid", + "inscription", + "##works", + "wednesday", + "chloe", + "maintains", + "panels", + "ashley", + "trucks", + "##nation", + "cluster", + "sunlight", + "strikes", + "zhang", + "##wing", + "dialect", + "canon", + "##ap", + "tucked", + "##ws", + "collecting", + "##mas", + "##can", + "##sville", + "maker", + "quoted", + "evan", + "franco", + "aria", + "buying", + "cleaning", + "eva", + "closet", + "provision", + "apollo", + "clinic", + "rat", + "##ez", + "necessarily", + "ac", + "##gle", + "##ising", + "venues", + "flipped", + "cent", + "spreading", + "trustees", + "checking", + "authorized", + "##sco", + "disappointed", + "##ado", + "notion", + "duration", + "trumpet", + "hesitated", + "topped", + "brussels", + "rolls", + "theoretical", + "hint", + "define", + "aggressive", + "repeat", + "wash", + "peaceful", + "optical", + "width", + "allegedly", + "mcdonald", + "strict", + "copyright", + "##illa", + "investors", + "mar", + "jam", + "witnesses", + "sounding", + "miranda", + "michelle", + "privacy", + "hugo", + "harmony", + "##pp", + "valid", + "lynn", + "glared", + "nina", + "102", + "headquartered", + "diving", + "boarding", + "gibson", + "##ncy", + "albanian", + "marsh", + "routine", + "dealt", + "enhanced", + "er", + "intelligent", + "substance", + "targeted", + "enlisted", + "discovers", + "spinning", + "observations", + "pissed", + "smoking", + "rebecca", + "capitol", + "visa", + "varied", + "costume", + "seemingly", + "indies", + "compensation", + "surgeon", + "thursday", + "arsenal", + "westminster", + "suburbs", + "rid", + "anglican", + "##ridge", + "knots", + "foods", + "alumni", + "lighter", + "fraser", + "whoever", + "portal", + "scandal", + "##ray", + "gavin", + "advised", + "instructor", + "flooding", + "terrorist", + "##ale", + "teenage", + "interim", + "senses", + "duck", + "teen", + "thesis", + "abby", + "eager", + "overcome", + "##ile", + "newport", + "glenn", + "rises", + "shame", + "##cc", + "prompted", + "priority", + "forgot", + "bomber", + "nicolas", + "protective", + "360", + "cartoon", + "katherine", + "breeze", + "lonely", + "trusted", + "henderson", + "richardson", + "relax", + "banner", + "candy", + "palms", + "remarkable", + "##rio", + "legends", + "cricketer", + "essay", + "ordained", + "edmund", + "rifles", + "trigger", + "##uri", + "##away", + "sail", + "alert", + "1830", + "audiences", + "penn", + "sussex", + "siblings", + "pursued", + "indianapolis", + "resist", + "rosa", + "consequence", + "succeed", + "avoided", + "1845", + "##ulation", + "inland", + "##tie", + "##nna", + "counsel", + "profession", + "chronicle", + "hurried", + "##una", + "eyebrow", + "eventual", + "bleeding", + "innovative", + "cure", + "##dom", + "committees", + "accounting", + "con", + "scope", + "hardy", + "heather", + "tenor", + "gut", + "herald", + "codes", + "tore", + "scales", + "wagon", + "##oo", + "luxury", + "tin", + "prefer", + "fountain", + "triangle", + "bonds", + "darling", + "convoy", + "dried", + "traced", + "beings", + "troy", + "accidentally", + "slam", + "findings", + "smelled", + "joey", + "lawyers", + "outcome", + "steep", + "bosnia", + "configuration", + "shifting", + "toll", + "brook", + "performers", + "lobby", + "philosophical", + "construct", + "shrine", + "aggregate", + "boot", + "cox", + "phenomenon", + "savage", + "insane", + "solely", + "reynolds", + "lifestyle", + "##ima", + "nationally", + "holdings", + "consideration", + "enable", + "edgar", + "mo", + "mama", + "##tein", + "fights", + "relegation", + "chances", + "atomic", + "hub", + "conjunction", + "awkward", + "reactions", + "currency", + "finale", + "kumar", + "underwent", + "steering", + "elaborate", + "gifts", + "comprising", + "melissa", + "veins", + "reasonable", + "sunshine", + "chi", + "solve", + "trails", + "inhabited", + "elimination", + "ethics", + "huh", + "ana", + "molly", + "consent", + "apartments", + "layout", + "marines", + "##ces", + "hunters", + "bulk", + "##oma", + "hometown", + "##wall", + "##mont", + "cracked", + "reads", + "neighbouring", + "withdrawn", + "admission", + "wingspan", + "damned", + "anthology", + "lancashire", + "brands", + "batting", + "forgive", + "cuban", + "awful", + "##lyn", + "104", + "dimensions", + "imagination", + "##ade", + "dante", + "##ship", + "tracking", + "desperately", + "goalkeeper", + "##yne", + "groaned", + "workshops", + "confident", + "burton", + "gerald", + "milton", + "circus", + "uncertain", + "slope", + "copenhagen", + "sophia", + "fog", + "philosopher", + "portraits", + "accent", + "cycling", + "varying", + "gripped", + "larvae", + "garrett", + "specified", + "scotia", + "mature", + "luther", + "kurt", + "rap", + "##kes", + "aerial", + "750", + "ferdinand", + "heated", + "es", + "transported", + "##shan", + "safely", + "nonetheless", + "##orn", + "##gal", + "motors", + "demanding", + "##sburg", + "startled", + "##brook", + "ally", + "generate", + "caps", + "ghana", + "stained", + "demo", + "mentions", + "beds", + "ap", + "afterward", + "diary", + "##bling", + "utility", + "##iro", + "richards", + "1837", + "conspiracy", + "conscious", + "shining", + "footsteps", + "observer", + "cyprus", + "urged", + "loyalty", + "developer", + "probability", + "olive", + "upgraded", + "gym", + "miracle", + "insects", + "graves", + "1844", + "ourselves", + "hydrogen", + "amazon", + "katie", + "tickets", + "poets", + "##pm", + "planes", + "##pan", + "prevention", + "witnessed", + "dense", + "jin", + "randy", + "tang", + "warehouse", + "monroe", + "bang", + "archived", + "elderly", + "investigations", + "alec", + "granite", + "mineral", + "conflicts", + "controlling", + "aboriginal", + "carlo", + "##zu", + "mechanics", + "stan", + "stark", + "rhode", + "skirt", + "est", + "##berry", + "bombs", + "respected", + "##horn", + "imposed", + "limestone", + "deny", + "nominee", + "memphis", + "grabbing", + "disabled", + "##als", + "amusement", + "aa", + "frankfurt", + "corn", + "referendum", + "varies", + "slowed", + "disk", + "firms", + "unconscious", + "incredible", + "clue", + "sue", + "##zhou", + "twist", + "##cio", + "joins", + "idaho", + "chad", + "developers", + "computing", + "destroyer", + "103", + "mortal", + "tucker", + "kingston", + "choices", + "yu", + "carson", + "1800", + "os", + "whitney", + "geneva", + "pretend", + "dimension", + "staged", + "plateau", + "maya", + "##une", + "freestyle", + "##bc", + "rovers", + "hiv", + "##ids", + "tristan", + "classroom", + "prospect", + "##hus", + "honestly", + "diploma", + "lied", + "thermal", + "auxiliary", + "feast", + "unlikely", + "iata", + "##tel", + "morocco", + "pounding", + "treasury", + "lithuania", + "considerably", + "1841", + "dish", + "1812", + "geological", + "matching", + "stumbled", + "destroying", + "marched", + "brien", + "advances", + "cake", + "nicole", + "belle", + "settling", + "measuring", + "directing", + "##mie", + "tuesday", + "bassist", + "capabilities", + "stunned", + "fraud", + "torpedo", + "##list", + "##phone", + "anton", + "wisdom", + "surveillance", + "ruined", + "##ulate", + "lawsuit", + "healthcare", + "theorem", + "halls", + "trend", + "aka", + "horizontal", + "dozens", + "acquire", + "lasting", + "swim", + "hawk", + "gorgeous", + "fees", + "vicinity", + "decrease", + "adoption", + "tactics", + "##ography", + "pakistani", + "##ole", + "draws", + "##hall", + "willie", + "burke", + "heath", + "algorithm", + "integral", + "powder", + "elliott", + "brigadier", + "jackie", + "tate", + "varieties", + "darker", + "##cho", + "lately", + "cigarette", + "specimens", + "adds", + "##ree", + "##ensis", + "##inger", + "exploded", + "finalist", + "cia", + "murders", + "wilderness", + "arguments", + "nicknamed", + "acceptance", + "onwards", + "manufacture", + "robertson", + "jets", + "tampa", + "enterprises", + "blog", + "loudly", + "composers", + "nominations", + "1838", + "ai", + "malta", + "inquiry", + "automobile", + "hosting", + "viii", + "rays", + "tilted", + "grief", + "museums", + "strategies", + "furious", + "euro", + "equality", + "cohen", + "poison", + "surrey", + "wireless", + "governed", + "ridiculous", + "moses", + "##esh", + "##room", + "vanished", + "##ito", + "barnes", + "attract", + "morrison", + "istanbul", + "##iness", + "absent", + "rotation", + "petition", + "janet", + "##logical", + "satisfaction", + "custody", + "deliberately", + "observatory", + "comedian", + "surfaces", + "pinyin", + "novelist", + "strictly", + "canterbury", + "oslo", + "monks", + "embrace", + "ibm", + "jealous", + "photograph", + "continent", + "dorothy", + "marina", + "doc", + "excess", + "holden", + "allegations", + "explaining", + "stack", + "avoiding", + "lance", + "storyline", + "majesty", + "poorly", + "spike", + "dos", + "bradford", + "raven", + "travis", + "classics", + "proven", + "voltage", + "pillow", + "fists", + "butt", + "1842", + "interpreted", + "##car", + "1839", + "gage", + "telegraph", + "lens", + "promising", + "expelled", + "casual", + "collector", + "zones", + "##min", + "silly", + "nintendo", + "##kh", + "##bra", + "downstairs", + "chef", + "suspicious", + "afl", + "flies", + "vacant", + "uganda", + "pregnancy", + "condemned", + "lutheran", + "estimates", + "cheap", + "decree", + "saxon", + "proximity", + "stripped", + "idiot", + "deposits", + "contrary", + "presenter", + "magnus", + "glacier", + "im", + "offense", + "edwin", + "##ori", + "upright", + "##long", + "bolt", + "##ois", + "toss", + "geographical", + "##izes", + "environments", + "delicate", + "marking", + "abstract", + "xavier", + "nails", + "windsor", + "plantation", + "occurring", + "equity", + "saskatchewan", + "fears", + "drifted", + "sequences", + "vegetation", + "revolt", + "##stic", + "1843", + "sooner", + "fusion", + "opposing", + "nato", + "skating", + "1836", + "secretly", + "ruin", + "lease", + "##oc", + "edit", + "##nne", + "flora", + "anxiety", + "ruby", + "##ological", + "##mia", + "tel", + "bout", + "taxi", + "emmy", + "frost", + "rainbow", + "compounds", + "foundations", + "rainfall", + "assassination", + "nightmare", + "dominican", + "##win", + "achievements", + "deserve", + "orlando", + "intact", + "armenia", + "##nte", + "calgary", + "valentine", + "106", + "marion", + "proclaimed", + "theodore", + "bells", + "courtyard", + "thigh", + "gonzalez", + "console", + "troop", + "minimal", + "monte", + "everyday", + "##ence", + "##if", + "supporter", + "terrorism", + "buck", + "openly", + "presbyterian", + "activists", + "carpet", + "##iers", + "rubbing", + "uprising", + "##yi", + "cute", + "conceived", + "legally", + "##cht", + "millennium", + "cello", + "velocity", + "ji", + "rescued", + "cardiff", + "1835", + "rex", + "concentrate", + "senators", + "beard", + "rendered", + "glowing", + "battalions", + "scouts", + "competitors", + "sculptor", + "catalogue", + "arctic", + "ion", + "raja", + "bicycle", + "wow", + "glancing", + "lawn", + "##woman", + "gentleman", + "lighthouse", + "publish", + "predicted", + "calculated", + "##val", + "variants", + "##gne", + "strain", + "##ui", + "winston", + "deceased", + "##nus", + "touchdowns", + "brady", + "caleb", + "sinking", + "echoed", + "crush", + "hon", + "blessed", + "protagonist", + "hayes", + "endangered", + "magnitude", + "editors", + "##tine", + "estimate", + "responsibilities", + "##mel", + "backup", + "laying", + "consumed", + "sealed", + "zurich", + "lovers", + "frustrated", + "##eau", + "ahmed", + "kicking", + "mit", + "treasurer", + "1832", + "biblical", + "refuse", + "terrified", + "pump", + "agrees", + "genuine", + "imprisonment", + "refuses", + "plymouth", + "##hen", + "lou", + "##nen", + "tara", + "trembling", + "antarctic", + "ton", + "learns", + "##tas", + "crap", + "crucial", + "faction", + "atop", + "##borough", + "wrap", + "lancaster", + "odds", + "hopkins", + "erik", + "lyon", + "##eon", + "bros", + "##ode", + "snap", + "locality", + "tips", + "empress", + "crowned", + "cal", + "acclaimed", + "chuckled", + "##ory", + "clara", + "sends", + "mild", + "towel", + "##fl", + "##day", + "##\u0430", + "wishing", + "assuming", + "interviewed", + "##bal", + "##die", + "interactions", + "eden", + "cups", + "helena", + "##lf", + "indie", + "beck", + "##fire", + "batteries", + "filipino", + "wizard", + "parted", + "##lam", + "traces", + "##born", + "rows", + "idol", + "albany", + "delegates", + "##ees", + "##sar", + "discussions", + "##ex", + "notre", + "instructed", + "belgrade", + "highways", + "suggestion", + "lauren", + "possess", + "orientation", + "alexandria", + "abdul", + "beats", + "salary", + "reunion", + "ludwig", + "alright", + "wagner", + "intimate", + "pockets", + "slovenia", + "hugged", + "brighton", + "merchants", + "cruel", + "stole", + "trek", + "slopes", + "repairs", + "enrollment", + "politically", + "underlying", + "promotional", + "counting", + "boeing", + "##bb", + "isabella", + "naming", + "##\u0438", + "keen", + "bacteria", + "listing", + "separately", + "belfast", + "ussr", + "450", + "lithuanian", + "anybody", + "ribs", + "sphere", + "martinez", + "cock", + "embarrassed", + "proposals", + "fragments", + "nationals", + "##fs", + "##wski", + "premises", + "fin", + "1500", + "alpine", + "matched", + "freely", + "bounded", + "jace", + "sleeve", + "##af", + "gaming", + "pier", + "populated", + "evident", + "##like", + "frances", + "flooded", + "##dle", + "frightened", + "pour", + "trainer", + "framed", + "visitor", + "challenging", + "pig", + "wickets", + "##fold", + "infected", + "email", + "##pes", + "arose", + "##aw", + "reward", + "ecuador", + "oblast", + "vale", + "ch", + "shuttle", + "##usa", + "bach", + "rankings", + "forbidden", + "cornwall", + "accordance", + "salem", + "consumers", + "bruno", + "fantastic", + "toes", + "machinery", + "resolved", + "julius", + "remembering", + "propaganda", + "iceland", + "bombardment", + "tide", + "contacts", + "wives", + "##rah", + "concerto", + "macdonald", + "albania", + "implement", + "daisy", + "tapped", + "sudan", + "helmet", + "angela", + "mistress", + "##lic", + "crop", + "sunk", + "finest", + "##craft", + "hostile", + "##ute", + "##tsu", + "boxer", + "fr", + "paths", + "adjusted", + "habit", + "ballot", + "supervision", + "soprano", + "##zen", + "bullets", + "wicked", + "sunset", + "regiments", + "disappear", + "lamp", + "performs", + "app", + "##gia", + "##oa", + "rabbit", + "digging", + "incidents", + "entries", + "##cion", + "dishes", + "##oi", + "introducing", + "##ati", + "##fied", + "freshman", + "slot", + "jill", + "tackles", + "baroque", + "backs", + "##iest", + "lone", + "sponsor", + "destiny", + "altogether", + "convert", + "##aro", + "consensus", + "shapes", + "demonstration", + "basically", + "feminist", + "auction", + "artifacts", + "##bing", + "strongest", + "twitter", + "halifax", + "2019", + "allmusic", + "mighty", + "smallest", + "precise", + "alexandra", + "viola", + "##los", + "##ille", + "manuscripts", + "##illo", + "dancers", + "ari", + "managers", + "monuments", + "blades", + "barracks", + "springfield", + "maiden", + "consolidated", + "electron", + "##end", + "berry", + "airing", + "wheat", + "nobel", + "inclusion", + "blair", + "payments", + "geography", + "bee", + "cc", + "eleanor", + "react", + "##hurst", + "afc", + "manitoba", + "##yu", + "su", + "lineup", + "fitness", + "recreational", + "investments", + "airborne", + "disappointment", + "##dis", + "edmonton", + "viewing", + "##row", + "renovation", + "##cast", + "infant", + "bankruptcy", + "roses", + "aftermath", + "pavilion", + "##yer", + "carpenter", + "withdrawal", + "ladder", + "##hy", + "discussing", + "popped", + "reliable", + "agreements", + "rochester", + "##abad", + "curves", + "bombers", + "220", + "rao", + "reverend", + "decreased", + "choosing", + "107", + "stiff", + "consulting", + "naples", + "crawford", + "tracy", + "ka", + "ribbon", + "cops", + "##lee", + "crushed", + "deciding", + "unified", + "teenager", + "accepting", + "flagship", + "explorer", + "poles", + "sanchez", + "inspection", + "revived", + "skilled", + "induced", + "exchanged", + "flee", + "locals", + "tragedy", + "swallow", + "loading", + "hanna", + "demonstrate", + "##ela", + "salvador", + "flown", + "contestants", + "civilization", + "##ines", + "wanna", + "rhodes", + "fletcher", + "hector", + "knocking", + "considers", + "##ough", + "nash", + "mechanisms", + "sensed", + "mentally", + "walt", + "unclear", + "##eus", + "renovated", + "madame", + "##cks", + "crews", + "governmental", + "##hin", + "undertaken", + "monkey", + "##ben", + "##ato", + "fatal", + "armored", + "copa", + "caves", + "governance", + "grasp", + "perception", + "certification", + "froze", + "damp", + "tugged", + "wyoming", + "##rg", + "##ero", + "newman", + "##lor", + "nerves", + "curiosity", + "graph", + "115", + "##ami", + "withdraw", + "tunnels", + "dull", + "meredith", + "moss", + "exhibits", + "neighbors", + "communicate", + "accuracy", + "explored", + "raiders", + "republicans", + "secular", + "kat", + "superman", + "penny", + "criticised", + "##tch", + "freed", + "update", + "conviction", + "wade", + "ham", + "likewise", + "delegation", + "gotta", + "doll", + "promises", + "technological", + "myth", + "nationality", + "resolve", + "convent", + "##mark", + "sharon", + "dig", + "sip", + "coordinator", + "entrepreneur", + "fold", + "##dine", + "capability", + "councillor", + "synonym", + "blown", + "swan", + "cursed", + "1815", + "jonas", + "haired", + "sofa", + "canvas", + "keeper", + "rivalry", + "##hart", + "rapper", + "speedway", + "swords", + "postal", + "maxwell", + "estonia", + "potter", + "recurring", + "##nn", + "##ave", + "errors", + "##oni", + "cognitive", + "1834", + "##\u00b2", + "claws", + "nadu", + "roberto", + "bce", + "wrestler", + "ellie", + "##ations", + "infinite", + "ink", + "##tia", + "presumably", + "finite", + "staircase", + "108", + "noel", + "patricia", + "nacional", + "##cation", + "chill", + "eternal", + "tu", + "preventing", + "prussia", + "fossil", + "limbs", + "##logist", + "ernst", + "frog", + "perez", + "rene", + "##ace", + "pizza", + "prussian", + "##ios", + "##vy", + "molecules", + "regulatory", + "answering", + "opinions", + "sworn", + "lengths", + "supposedly", + "hypothesis", + "upward", + "habitats", + "seating", + "ancestors", + "drank", + "yield", + "hd", + "synthesis", + "researcher", + "modest", + "##var", + "mothers", + "peered", + "voluntary", + "homeland", + "##the", + "acclaim", + "##igan", + "static", + "valve", + "luxembourg", + "alto", + "carroll", + "fe", + "receptor", + "norton", + "ambulance", + "##tian", + "johnston", + "catholics", + "depicting", + "jointly", + "elephant", + "gloria", + "mentor", + "badge", + "ahmad", + "distinguish", + "remarked", + "councils", + "precisely", + "allison", + "advancing", + "detection", + "crowded", + "##10", + "cooperative", + "ankle", + "mercedes", + "dagger", + "surrendered", + "pollution", + "commit", + "subway", + "jeffrey", + "lesson", + "sculptures", + "provider", + "##fication", + "membrane", + "timothy", + "rectangular", + "fiscal", + "heating", + "teammate", + "basket", + "particle", + "anonymous", + "deployment", + "##ple", + "missiles", + "courthouse", + "proportion", + "shoe", + "sec", + "##ller", + "complaints", + "forbes", + "blacks", + "abandon", + "remind", + "sizes", + "overwhelming", + "autobiography", + "natalie", + "##awa", + "risks", + "contestant", + "countryside", + "babies", + "scorer", + "invaded", + "enclosed", + "proceed", + "hurling", + "disorders", + "##cu", + "reflecting", + "continuously", + "cruiser", + "graduates", + "freeway", + "investigated", + "ore", + "deserved", + "maid", + "blocking", + "phillip", + "jorge", + "shakes", + "dove", + "mann", + "variables", + "lacked", + "burden", + "accompanying", + "que", + "consistently", + "organizing", + "provisional", + "complained", + "endless", + "##rm", + "tubes", + "juice", + "georges", + "krishna", + "mick", + "labels", + "thriller", + "##uch", + "laps", + "arcade", + "sage", + "snail", + "##table", + "shannon", + "fi", + "laurence", + "seoul", + "vacation", + "presenting", + "hire", + "churchill", + "surprisingly", + "prohibited", + "savannah", + "technically", + "##oli", + "170", + "##lessly", + "testimony", + "suited", + "speeds", + "toys", + "romans", + "mlb", + "flowering", + "measurement", + "talented", + "kay", + "settings", + "charleston", + "expectations", + "shattered", + "achieving", + "triumph", + "ceremonies", + "portsmouth", + "lanes", + "mandatory", + "loser", + "stretching", + "cologne", + "realizes", + "seventy", + "cornell", + "careers", + "webb", + "##ulating", + "americas", + "budapest", + "ava", + "suspicion", + "##ison", + "yo", + "conrad", + "##hai", + "sterling", + "jessie", + "rector", + "##az", + "1831", + "transform", + "organize", + "loans", + "christine", + "volcanic", + "warrant", + "slender", + "summers", + "subfamily", + "newer", + "danced", + "dynamics", + "rhine", + "proceeds", + "heinrich", + "gastropod", + "commands", + "sings", + "facilitate", + "easter", + "ra", + "positioned", + "responses", + "expense", + "fruits", + "yanked", + "imported", + "25th", + "velvet", + "vic", + "primitive", + "tribune", + "baldwin", + "neighbourhood", + "donna", + "rip", + "hay", + "pr", + "##uro", + "1814", + "espn", + "welcomed", + "##aria", + "qualifier", + "glare", + "highland", + "timing", + "##cted", + "shells", + "eased", + "geometry", + "louder", + "exciting", + "slovakia", + "##sion", + "##iz", + "##lot", + "savings", + "prairie", + "##ques", + "marching", + "rafael", + "tonnes", + "##lled", + "curtain", + "preceding", + "shy", + "heal", + "greene", + "worthy", + "##pot", + "detachment", + "bury", + "sherman", + "##eck", + "reinforced", + "seeks", + "bottles", + "contracted", + "duchess", + "outfit", + "walsh", + "##sc", + "mickey", + "##ase", + "geoffrey", + "archer", + "squeeze", + "dawson", + "eliminate", + "invention", + "##enberg", + "neal", + "##eth", + "stance", + "dealer", + "coral", + "maple", + "retire", + "polo", + "simplified", + "##ht", + "1833", + "hid", + "watts", + "backwards", + "jules", + "##oke", + "genesis", + "mt", + "frames", + "rebounds", + "burma", + "woodland", + "moist", + "santos", + "whispers", + "drained", + "subspecies", + "##aa", + "streaming", + "ulster", + "burnt", + "correspondence", + "maternal", + "gerard", + "denis", + "stealing", + "##load", + "genius", + "duchy", + "##oria", + "inaugurated", + "momentum", + "suits", + "placement", + "sovereign", + "clause", + "thames", + "##hara", + "confederation", + "reservation", + "sketch", + "yankees", + "lets", + "rotten", + "charm", + "hal", + "verses", + "ultra", + "commercially", + "dot", + "salon", + "citation", + "adopt", + "winnipeg", + "mist", + "allocated", + "cairo", + "##boy", + "jenkins", + "interference", + "objectives", + "##wind", + "1820", + "portfolio", + "armoured", + "sectors", + "##eh", + "initiatives", + "##world", + "integrity", + "exercises", + "robe", + "tap", + "ab", + "gazed", + "##tones", + "distracted", + "rulers", + "111", + "favorable", + "jerome", + "tended", + "cart", + "factories", + "##eri", + "diplomat", + "valued", + "gravel", + "charitable", + "##try", + "calvin", + "exploring", + "chang", + "shepherd", + "terrace", + "pdf", + "pupil", + "##ural", + "reflects", + "ups", + "##rch", + "governors", + "shelf", + "depths", + "##nberg", + "trailed", + "crest", + "tackle", + "##nian", + "##ats", + "hatred", + "##kai", + "clare", + "makers", + "ethiopia", + "longtime", + "detected", + "embedded", + "lacking", + "slapped", + "rely", + "thomson", + "anticipation", + "iso", + "morton", + "successive", + "agnes", + "screenwriter", + "straightened", + "philippe", + "playwright", + "haunted", + "licence", + "iris", + "intentions", + "sutton", + "112", + "logical", + "correctly", + "##weight", + "branded", + "licked", + "tipped", + "silva", + "ricky", + "narrator", + "requests", + "##ents", + "greeted", + "supernatural", + "cow", + "##wald", + "lung", + "refusing", + "employer", + "strait", + "gaelic", + "liner", + "##piece", + "zoe", + "sabha", + "##mba", + "driveway", + "harvest", + "prints", + "bates", + "reluctantly", + "threshold", + "algebra", + "ira", + "wherever", + "coupled", + "240", + "assumption", + "picks", + "##air", + "designers", + "raids", + "gentlemen", + "##ean", + "roller", + "blowing", + "leipzig", + "locks", + "screw", + "dressing", + "strand", + "##lings", + "scar", + "dwarf", + "depicts", + "##nu", + "nods", + "##mine", + "differ", + "boris", + "##eur", + "yuan", + "flip", + "##gie", + "mob", + "invested", + "questioning", + "applying", + "##ture", + "shout", + "##sel", + "gameplay", + "blamed", + "illustrations", + "bothered", + "weakness", + "rehabilitation", + "##of", + "##zes", + "envelope", + "rumors", + "miners", + "leicester", + "subtle", + "kerry", + "##ico", + "ferguson", + "##fu", + "premiership", + "ne", + "##cat", + "bengali", + "prof", + "catches", + "remnants", + "dana", + "##rily", + "shouting", + "presidents", + "baltic", + "ought", + "ghosts", + "dances", + "sailors", + "shirley", + "fancy", + "dominic", + "##bie", + "madonna", + "##rick", + "bark", + "buttons", + "gymnasium", + "ashes", + "liver", + "toby", + "oath", + "providence", + "doyle", + "evangelical", + "nixon", + "cement", + "carnegie", + "embarked", + "hatch", + "surroundings", + "guarantee", + "needing", + "pirate", + "essence", + "##bee", + "filter", + "crane", + "hammond", + "projected", + "immune", + "percy", + "twelfth", + "##ult", + "regent", + "doctoral", + "damon", + "mikhail", + "##ichi", + "lu", + "critically", + "elect", + "realised", + "abortion", + "acute", + "screening", + "mythology", + "steadily", + "##fc", + "frown", + "nottingham", + "kirk", + "wa", + "minneapolis", + "##rra", + "module", + "algeria", + "mc", + "nautical", + "encounters", + "surprising", + "statues", + "availability", + "shirts", + "pie", + "alma", + "brows", + "munster", + "mack", + "soup", + "crater", + "tornado", + "sanskrit", + "cedar", + "explosive", + "bordered", + "dixon", + "planets", + "stamp", + "exam", + "happily", + "##bble", + "carriers", + "kidnapped", + "##vis", + "accommodation", + "emigrated", + "##met", + "knockout", + "correspondent", + "violation", + "profits", + "peaks", + "lang", + "specimen", + "agenda", + "ancestry", + "pottery", + "spelling", + "equations", + "obtaining", + "ki", + "linking", + "1825", + "debris", + "asylum", + "##20", + "buddhism", + "teddy", + "##ants", + "gazette", + "##nger", + "##sse", + "dental", + "eligibility", + "utc", + "fathers", + "averaged", + "zimbabwe", + "francesco", + "coloured", + "hissed", + "translator", + "lynch", + "mandate", + "humanities", + "mackenzie", + "uniforms", + "lin", + "##iana", + "##gio", + "asset", + "mhz", + "fitting", + "samantha", + "genera", + "wei", + "rim", + "beloved", + "shark", + "riot", + "entities", + "expressions", + "indo", + "carmen", + "slipping", + "owing", + "abbot", + "neighbor", + "sidney", + "##av", + "rats", + "recommendations", + "encouraging", + "squadrons", + "anticipated", + "commanders", + "conquered", + "##oto", + "donations", + "diagnosed", + "##mond", + "divide", + "##iva", + "guessed", + "decoration", + "vernon", + "auditorium", + "revelation", + "conversations", + "##kers", + "##power", + "herzegovina", + "dash", + "alike", + "protested", + "lateral", + "herman", + "accredited", + "mg", + "##gent", + "freeman", + "mel", + "fiji", + "crow", + "crimson", + "##rine", + "livestock", + "##pped", + "humanitarian", + "bored", + "oz", + "whip", + "##lene", + "##ali", + "legitimate", + "alter", + "grinning", + "spelled", + "anxious", + "oriental", + "wesley", + "##nin", + "##hole", + "carnival", + "controller", + "detect", + "##ssa", + "bowed", + "educator", + "kosovo", + "macedonia", + "##sin", + "occupy", + "mastering", + "stephanie", + "janeiro", + "para", + "unaware", + "nurses", + "noon", + "135", + "cam", + "hopefully", + "ranger", + "combine", + "sociology", + "polar", + "rica", + "##eer", + "neill", + "##sman", + "holocaust", + "##ip", + "doubled", + "lust", + "1828", + "109", + "decent", + "cooling", + "unveiled", + "##card", + "1829", + "nsw", + "homer", + "chapman", + "meyer", + "##gin", + "dive", + "mae", + "reagan", + "expertise", + "##gled", + "darwin", + "brooke", + "sided", + "prosecution", + "investigating", + "comprised", + "petroleum", + "genres", + "reluctant", + "differently", + "trilogy", + "johns", + "vegetables", + "corpse", + "highlighted", + "lounge", + "pension", + "unsuccessfully", + "elegant", + "aided", + "ivory", + "beatles", + "amelia", + "cain", + "dubai", + "sunny", + "immigrant", + "babe", + "click", + "##nder", + "underwater", + "pepper", + "combining", + "mumbled", + "atlas", + "horns", + "accessed", + "ballad", + "physicians", + "homeless", + "gestured", + "rpm", + "freak", + "louisville", + "corporations", + "patriots", + "prizes", + "rational", + "warn", + "modes", + "decorative", + "overnight", + "din", + "troubled", + "phantom", + "##ort", + "monarch", + "sheer", + "##dorf", + "generals", + "guidelines", + "organs", + "addresses", + "##zon", + "enhance", + "curling", + "parishes", + "cord", + "##kie", + "linux", + "caesar", + "deutsche", + "bavaria", + "##bia", + "coleman", + "cyclone", + "##eria", + "bacon", + "petty", + "##yama", + "##old", + "hampton", + "diagnosis", + "1824", + "throws", + "complexity", + "rita", + "disputed", + "##\u2083", + "pablo", + "##sch", + "marketed", + "trafficking", + "##ulus", + "examine", + "plague", + "formats", + "##oh", + "vault", + "faithful", + "##bourne", + "webster", + "##ox", + "highlights", + "##ient", + "##ann", + "phones", + "vacuum", + "sandwich", + "modeling", + "##gated", + "bolivia", + "clergy", + "qualities", + "isabel", + "##nas", + "##ars", + "wears", + "screams", + "reunited", + "annoyed", + "bra", + "##ancy", + "##rate", + "differential", + "transmitter", + "tattoo", + "container", + "poker", + "##och", + "excessive", + "resides", + "cowboys", + "##tum", + "augustus", + "trash", + "providers", + "statute", + "retreated", + "balcony", + "reversed", + "void", + "storey", + "preceded", + "masses", + "leap", + "laughs", + "neighborhoods", + "wards", + "schemes", + "falcon", + "santo", + "battlefield", + "pad", + "ronnie", + "thread", + "lesbian", + "venus", + "##dian", + "beg", + "sandstone", + "daylight", + "punched", + "gwen", + "analog", + "stroked", + "wwe", + "acceptable", + "measurements", + "dec", + "toxic", + "##kel", + "adequate", + "surgical", + "economist", + "parameters", + "varsity", + "##sberg", + "quantity", + "ella", + "##chy", + "##rton", + "countess", + "generating", + "precision", + "diamonds", + "expressway", + "ga", + "##\u0131", + "1821", + "uruguay", + "talents", + "galleries", + "expenses", + "scanned", + "colleague", + "outlets", + "ryder", + "lucien", + "##ila", + "paramount", + "##bon", + "syracuse", + "dim", + "fangs", + "gown", + "sweep", + "##sie", + "toyota", + "missionaries", + "websites", + "##nsis", + "sentences", + "adviser", + "val", + "trademark", + "spells", + "##plane", + "patience", + "starter", + "slim", + "##borg", + "toe", + "incredibly", + "shoots", + "elliot", + "nobility", + "##wyn", + "cowboy", + "endorsed", + "gardner", + "tendency", + "persuaded", + "organisms", + "emissions", + "kazakhstan", + "amused", + "boring", + "chips", + "themed", + "##hand", + "llc", + "constantinople", + "chasing", + "systematic", + "guatemala", + "borrowed", + "erin", + "carey", + "##hard", + "highlands", + "struggles", + "1810", + "##ifying", + "##ced", + "wong", + "exceptions", + "develops", + "enlarged", + "kindergarten", + "castro", + "##ern", + "##rina", + "leigh", + "zombie", + "juvenile", + "##most", + "consul", + "##nar", + "sailor", + "hyde", + "clarence", + "intensive", + "pinned", + "nasty", + "useless", + "jung", + "clayton", + "stuffed", + "exceptional", + "ix", + "apostolic", + "230", + "transactions", + "##dge", + "exempt", + "swinging", + "cove", + "religions", + "##ash", + "shields", + "dairy", + "bypass", + "190", + "pursuing", + "bug", + "joyce", + "bombay", + "chassis", + "southampton", + "chat", + "interact", + "redesignated", + "##pen", + "nascar", + "pray", + "salmon", + "rigid", + "regained", + "malaysian", + "grim", + "publicity", + "constituted", + "capturing", + "toilet", + "delegate", + "purely", + "tray", + "drift", + "loosely", + "striker", + "weakened", + "trinidad", + "mitch", + "itv", + "defines", + "transmitted", + "ming", + "scarlet", + "nodding", + "fitzgerald", + "fu", + "narrowly", + "sp", + "tooth", + "standings", + "virtue", + "##\u2081", + "##wara", + "##cting", + "chateau", + "gloves", + "lid", + "##nel", + "hurting", + "conservatory", + "##pel", + "sinclair", + "reopened", + "sympathy", + "nigerian", + "strode", + "advocated", + "optional", + "chronic", + "discharge", + "##rc", + "suck", + "compatible", + "laurel", + "stella", + "shi", + "fails", + "wage", + "dodge", + "128", + "informal", + "sorts", + "levi", + "buddha", + "villagers", + "##aka", + "chronicles", + "heavier", + "summoned", + "gateway", + "3000", + "eleventh", + "jewelry", + "translations", + "accordingly", + "seas", + "##ency", + "fiber", + "pyramid", + "cubic", + "dragging", + "##ista", + "caring", + "##ops", + "android", + "contacted", + "lunar", + "##dt", + "kai", + "lisbon", + "patted", + "1826", + "sacramento", + "theft", + "madagascar", + "subtropical", + "disputes", + "ta", + "holidays", + "piper", + "willow", + "mare", + "cane", + "itunes", + "newfoundland", + "benny", + "companions", + "dong", + "raj", + "observe", + "roar", + "charming", + "plaque", + "tibetan", + "fossils", + "enacted", + "manning", + "bubble", + "tina", + "tanzania", + "##eda", + "##hir", + "funk", + "swamp", + "deputies", + "cloak", + "ufc", + "scenario", + "par", + "scratch", + "metals", + "anthem", + "guru", + "engaging", + "specially", + "##boat", + "dialects", + "nineteen", + "cecil", + "duet", + "disability", + "messenger", + "unofficial", + "##lies", + "defunct", + "eds", + "moonlight", + "drainage", + "surname", + "puzzle", + "honda", + "switching", + "conservatives", + "mammals", + "knox", + "broadcaster", + "sidewalk", + "cope", + "##ried", + "benson", + "princes", + "peterson", + "##sal", + "bedford", + "sharks", + "eli", + "wreck", + "alberto", + "gasp", + "archaeology", + "lgbt", + "teaches", + "securities", + "madness", + "compromise", + "waving", + "coordination", + "davidson", + "visions", + "leased", + "possibilities", + "eighty", + "jun", + "fernandez", + "enthusiasm", + "assassin", + "sponsorship", + "reviewer", + "kingdoms", + "estonian", + "laboratories", + "##fy", + "##nal", + "applies", + "verb", + "celebrations", + "##zzo", + "rowing", + "lightweight", + "sadness", + "submit", + "mvp", + "balanced", + "dude", + "##vas", + "explicitly", + "metric", + "magnificent", + "mound", + "brett", + "mohammad", + "mistakes", + "irregular", + "##hing", + "##ass", + "sanders", + "betrayed", + "shipped", + "surge", + "##enburg", + "reporters", + "termed", + "georg", + "pity", + "verbal", + "bulls", + "abbreviated", + "enabling", + "appealed", + "##are", + "##atic", + "sicily", + "sting", + "heel", + "sweetheart", + "bart", + "spacecraft", + "brutal", + "monarchy", + "##tter", + "aberdeen", + "cameo", + "diane", + "##ub", + "survivor", + "clyde", + "##aries", + "complaint", + "##makers", + "clarinet", + "delicious", + "chilean", + "karnataka", + "coordinates", + "1818", + "panties", + "##rst", + "pretending", + "ar", + "dramatically", + "kiev", + "bella", + "tends", + "distances", + "113", + "catalog", + "launching", + "instances", + "telecommunications", + "portable", + "lindsay", + "vatican", + "##eim", + "angles", + "aliens", + "marker", + "stint", + "screens", + "bolton", + "##rne", + "judy", + "wool", + "benedict", + "plasma", + "europa", + "spark", + "imaging", + "filmmaker", + "swiftly", + "##een", + "contributor", + "##nor", + "opted", + "stamps", + "apologize", + "financing", + "butter", + "gideon", + "sophisticated", + "alignment", + "avery", + "chemicals", + "yearly", + "speculation", + "prominence", + "professionally", + "##ils", + "immortal", + "institutional", + "inception", + "wrists", + "identifying", + "tribunal", + "derives", + "gains", + "##wo", + "papal", + "preference", + "linguistic", + "vince", + "operative", + "brewery", + "##ont", + "unemployment", + "boyd", + "##ured", + "##outs", + "albeit", + "prophet", + "1813", + "bi", + "##rr", + "##face", + "##rad", + "quarterly", + "asteroid", + "cleaned", + "radius", + "temper", + "##llen", + "telugu", + "jerk", + "viscount", + "menu", + "##ote", + "glimpse", + "##aya", + "yacht", + "hawaiian", + "baden", + "##rl", + "laptop", + "readily", + "##gu", + "monetary", + "offshore", + "scots", + "watches", + "##yang", + "##arian", + "upgrade", + "needle", + "xbox", + "lea", + "encyclopedia", + "flank", + "fingertips", + "##pus", + "delight", + "teachings", + "confirm", + "roth", + "beaches", + "midway", + "winters", + "##iah", + "teasing", + "daytime", + "beverly", + "gambling", + "bonnie", + "##backs", + "regulated", + "clement", + "hermann", + "tricks", + "knot", + "##shing", + "##uring", + "##vre", + "detached", + "ecological", + "owed", + "specialty", + "byron", + "inventor", + "bats", + "stays", + "screened", + "unesco", + "midland", + "trim", + "affection", + "##ander", + "##rry", + "jess", + "thoroughly", + "feedback", + "##uma", + "chennai", + "strained", + "heartbeat", + "wrapping", + "overtime", + "pleaded", + "##sworth", + "mon", + "leisure", + "oclc", + "##tate", + "##ele", + "feathers", + "angelo", + "thirds", + "nuts", + "surveys", + "clever", + "gill", + "commentator", + "##dos", + "darren", + "rides", + "gibraltar", + "##nc", + "##mu", + "dissolution", + "dedication", + "shin", + "meals", + "saddle", + "elvis", + "reds", + "chaired", + "taller", + "appreciation", + "functioning", + "niece", + "favored", + "advocacy", + "robbie", + "criminals", + "suffolk", + "yugoslav", + "passport", + "constable", + "congressman", + "hastings", + "vera", + "##rov", + "consecrated", + "sparks", + "ecclesiastical", + "confined", + "##ovich", + "muller", + "floyd", + "nora", + "1822", + "paved", + "1827", + "cumberland", + "ned", + "saga", + "spiral", + "##flow", + "appreciated", + "yi", + "collaborative", + "treating", + "similarities", + "feminine", + "finishes", + "##ib", + "jade", + "import", + "##nse", + "##hot", + "champagne", + "mice", + "securing", + "celebrities", + "helsinki", + "attributes", + "##gos", + "cousins", + "phases", + "ache", + "lucia", + "gandhi", + "submission", + "vicar", + "spear", + "shine", + "tasmania", + "biting", + "detention", + "constitute", + "tighter", + "seasonal", + "##gus", + "terrestrial", + "matthews", + "##oka", + "effectiveness", + "parody", + "philharmonic", + "##onic", + "1816", + "strangers", + "encoded", + "consortium", + "guaranteed", + "regards", + "shifts", + "tortured", + "collision", + "supervisor", + "inform", + "broader", + "insight", + "theaters", + "armour", + "emeritus", + "blink", + "incorporates", + "mapping", + "##50", + "##ein", + "handball", + "flexible", + "##nta", + "substantially", + "generous", + "thief", + "##own", + "carr", + "loses", + "1793", + "prose", + "ucla", + "romeo", + "generic", + "metallic", + "realization", + "damages", + "mk", + "commissioners", + "zach", + "default", + "##ther", + "helicopters", + "lengthy", + "stems", + "spa", + "partnered", + "spectators", + "rogue", + "indication", + "penalties", + "teresa", + "1801", + "sen", + "##tric", + "dalton", + "##wich", + "irving", + "photographic", + "##vey", + "dell", + "deaf", + "peters", + "excluded", + "unsure", + "##vable", + "patterson", + "crawled", + "##zio", + "resided", + "whipped", + "latvia", + "slower", + "ecole", + "pipes", + "employers", + "maharashtra", + "comparable", + "va", + "textile", + "pageant", + "##gel", + "alphabet", + "binary", + "irrigation", + "chartered", + "choked", + "antoine", + "offs", + "waking", + "supplement", + "##wen", + "quantities", + "demolition", + "regain", + "locate", + "urdu", + "folks", + "alt", + "114", + "##mc", + "scary", + "andreas", + "whites", + "##ava", + "classrooms", + "mw", + "aesthetic", + "publishes", + "valleys", + "guides", + "cubs", + "johannes", + "bryant", + "conventions", + "affecting", + "##itt", + "drain", + "awesome", + "isolation", + "prosecutor", + "ambitious", + "apology", + "captive", + "downs", + "atmospheric", + "lorenzo", + "aisle", + "beef", + "foul", + "##onia", + "kidding", + "composite", + "disturbed", + "illusion", + "natives", + "##ffer", + "emi", + "rockets", + "riverside", + "wartime", + "painters", + "adolf", + "melted", + "##ail", + "uncertainty", + "simulation", + "hawks", + "progressed", + "meantime", + "builder", + "spray", + "breach", + "unhappy", + "regina", + "russians", + "##urg", + "determining", + "##tation", + "tram", + "1806", + "##quin", + "aging", + "##12", + "1823", + "garion", + "rented", + "mister", + "diaz", + "terminated", + "clip", + "1817", + "depend", + "nervously", + "disco", + "owe", + "defenders", + "shiva", + "notorious", + "disbelief", + "shiny", + "worcester", + "##gation", + "##yr", + "trailing", + "undertook", + "islander", + "belarus", + "limitations", + "watershed", + "fuller", + "overlooking", + "utilized", + "raphael", + "1819", + "synthetic", + "breakdown", + "klein", + "##nate", + "moaned", + "memoir", + "lamb", + "practicing", + "##erly", + "cellular", + "arrows", + "exotic", + "##graphy", + "witches", + "117", + "charted", + "rey", + "hut", + "hierarchy", + "subdivision", + "freshwater", + "giuseppe", + "aloud", + "reyes", + "qatar", + "marty", + "sideways", + "utterly", + "sexually", + "jude", + "prayers", + "mccarthy", + "softball", + "blend", + "damien", + "##gging", + "##metric", + "wholly", + "erupted", + "lebanese", + "negro", + "revenues", + "tasted", + "comparative", + "teamed", + "transaction", + "labeled", + "maori", + "sovereignty", + "parkway", + "trauma", + "gran", + "malay", + "121", + "advancement", + "descendant", + "2020", + "buzz", + "salvation", + "inventory", + "symbolic", + "##making", + "antarctica", + "mps", + "##gas", + "##bro", + "mohammed", + "myanmar", + "holt", + "submarines", + "tones", + "##lman", + "locker", + "patriarch", + "bangkok", + "emerson", + "remarks", + "predators", + "kin", + "afghan", + "confession", + "norwich", + "rental", + "emerge", + "advantages", + "##zel", + "rca", + "##hold", + "shortened", + "storms", + "aidan", + "##matic", + "autonomy", + "compliance", + "##quet", + "dudley", + "atp", + "##osis", + "1803", + "motto", + "documentation", + "summary", + "professors", + "spectacular", + "christina", + "archdiocese", + "flashing", + "innocence", + "remake", + "##dell", + "psychic", + "reef", + "scare", + "employ", + "rs", + "sticks", + "meg", + "gus", + "leans", + "##ude", + "accompany", + "bergen", + "tomas", + "##iko", + "doom", + "wages", + "pools", + "##nch", + "##bes", + "breasts", + "scholarly", + "alison", + "outline", + "brittany", + "breakthrough", + "willis", + "realistic", + "##cut", + "##boro", + "competitor", + "##stan", + "pike", + "picnic", + "icon", + "designing", + "commercials", + "washing", + "villain", + "skiing", + "micro", + "costumes", + "auburn", + "halted", + "executives", + "##hat", + "logistics", + "cycles", + "vowel", + "applicable", + "barrett", + "exclaimed", + "eurovision", + "eternity", + "ramon", + "##umi", + "##lls", + "modifications", + "sweeping", + "disgust", + "##uck", + "torch", + "aviv", + "ensuring", + "rude", + "dusty", + "sonic", + "donovan", + "outskirts", + "cu", + "pathway", + "##band", + "##gun", + "##lines", + "disciplines", + "acids", + "cadet", + "paired", + "##40", + "sketches", + "##sive", + "marriages", + "##\u207a", + "folding", + "peers", + "slovak", + "implies", + "admired", + "##beck", + "1880s", + "leopold", + "instinct", + "attained", + "weston", + "megan", + "horace", + "##ination", + "dorsal", + "ingredients", + "evolutionary", + "##its", + "complications", + "deity", + "lethal", + "brushing", + "levy", + "deserted", + "institutes", + "posthumously", + "delivering", + "telescope", + "coronation", + "motivated", + "rapids", + "luc", + "flicked", + "pays", + "volcano", + "tanner", + "weighed", + "##nica", + "crowds", + "frankie", + "gifted", + "addressing", + "granddaughter", + "winding", + "##rna", + "constantine", + "gomez", + "##front", + "landscapes", + "rudolf", + "anthropology", + "slate", + "werewolf", + "##lio", + "astronomy", + "circa", + "rouge", + "dreaming", + "sack", + "knelt", + "drowned", + "naomi", + "prolific", + "tracked", + "freezing", + "herb", + "##dium", + "agony", + "randall", + "twisting", + "wendy", + "deposit", + "touches", + "vein", + "wheeler", + "##bbled", + "##bor", + "batted", + "retaining", + "tire", + "presently", + "compare", + "specification", + "daemon", + "nigel", + "##grave", + "merry", + "recommendation", + "czechoslovakia", + "sandra", + "ng", + "roma", + "##sts", + "lambert", + "inheritance", + "sheikh", + "winchester", + "cries", + "examining", + "##yle", + "comeback", + "cuisine", + "nave", + "##iv", + "ko", + "retrieve", + "tomatoes", + "barker", + "polished", + "defining", + "irene", + "lantern", + "personalities", + "begging", + "tract", + "swore", + "1809", + "175", + "##gic", + "omaha", + "brotherhood", + "##rley", + "haiti", + "##ots", + "exeter", + "##ete", + "##zia", + "steele", + "dumb", + "pearson", + "210", + "surveyed", + "elisabeth", + "trends", + "##ef", + "fritz", + "##rf", + "premium", + "bugs", + "fraction", + "calmly", + "viking", + "##birds", + "tug", + "inserted", + "unusually", + "##ield", + "confronted", + "distress", + "crashing", + "brent", + "turks", + "resign", + "##olo", + "cambodia", + "gabe", + "sauce", + "##kal", + "evelyn", + "116", + "extant", + "clusters", + "quarry", + "teenagers", + "luna", + "##lers", + "##ister", + "affiliation", + "drill", + "##ashi", + "panthers", + "scenic", + "libya", + "anita", + "strengthen", + "inscriptions", + "##cated", + "lace", + "sued", + "judith", + "riots", + "##uted", + "mint", + "##eta", + "preparations", + "midst", + "dub", + "challenger", + "##vich", + "mock", + "cf", + "displaced", + "wicket", + "breaths", + "enables", + "schmidt", + "analyst", + "##lum", + "ag", + "highlight", + "automotive", + "axe", + "josef", + "newark", + "sufficiently", + "resembles", + "50th", + "##pal", + "flushed", + "mum", + "traits", + "##ante", + "commodore", + "incomplete", + "warming", + "titular", + "ceremonial", + "ethical", + "118", + "celebrating", + "eighteenth", + "cao", + "lima", + "medalist", + "mobility", + "strips", + "snakes", + "##city", + "miniature", + "zagreb", + "barton", + "escapes", + "umbrella", + "automated", + "doubted", + "differs", + "cooled", + "georgetown", + "dresden", + "cooked", + "fade", + "wyatt", + "rna", + "jacobs", + "carlton", + "abundant", + "stereo", + "boost", + "madras", + "inning", + "##hia", + "spur", + "ip", + "malayalam", + "begged", + "osaka", + "groan", + "escaping", + "charging", + "dose", + "vista", + "##aj", + "bud", + "papa", + "communists", + "advocates", + "edged", + "tri", + "##cent", + "resemble", + "peaking", + "necklace", + "fried", + "montenegro", + "saxony", + "goose", + "glances", + "stuttgart", + "curator", + "recruit", + "grocery", + "sympathetic", + "##tting", + "##fort", + "127", + "lotus", + "randolph", + "ancestor", + "##rand", + "succeeding", + "jupiter", + "1798", + "macedonian", + "##heads", + "hiking", + "1808", + "handing", + "fischer", + "##itive", + "garbage", + "node", + "##pies", + "prone", + "singular", + "papua", + "inclined", + "attractions", + "italia", + "pouring", + "motioned", + "grandma", + "garnered", + "jacksonville", + "corp", + "ego", + "ringing", + "aluminum", + "##hausen", + "ordering", + "##foot", + "drawer", + "traders", + "synagogue", + "##play", + "##kawa", + "resistant", + "wandering", + "fragile", + "fiona", + "teased", + "var", + "hardcore", + "soaked", + "jubilee", + "decisive", + "exposition", + "mercer", + "poster", + "valencia", + "hale", + "kuwait", + "1811", + "##ises", + "##wr", + "##eed", + "tavern", + "gamma", + "122", + "johan", + "##uer", + "airways", + "amino", + "gil", + "##ury", + "vocational", + "domains", + "torres", + "##sp", + "generator", + "folklore", + "outcomes", + "##keeper", + "canberra", + "shooter", + "fl", + "beams", + "confrontation", + "##lling", + "##gram", + "feb", + "aligned", + "forestry", + "pipeline", + "jax", + "motorway", + "conception", + "decay", + "##tos", + "coffin", + "##cott", + "stalin", + "1805", + "escorted", + "minded", + "##nam", + "sitcom", + "purchasing", + "twilight", + "veronica", + "additions", + "passive", + "tensions", + "straw", + "123", + "frequencies", + "1804", + "refugee", + "cultivation", + "##iate", + "christie", + "clary", + "bulletin", + "crept", + "disposal", + "##rich", + "##zong", + "processor", + "crescent", + "##rol", + "bmw", + "emphasized", + "whale", + "nazis", + "aurora", + "##eng", + "dwelling", + "hauled", + "sponsors", + "toledo", + "mega", + "ideology", + "theatres", + "tessa", + "cerambycidae", + "saves", + "turtle", + "cone", + "suspects", + "kara", + "rusty", + "yelling", + "greeks", + "mozart", + "shades", + "cocked", + "participant", + "##tro", + "shire", + "spit", + "freeze", + "necessity", + "##cos", + "inmates", + "nielsen", + "councillors", + "loaned", + "uncommon", + "omar", + "peasants", + "botanical", + "offspring", + "daniels", + "formations", + "jokes", + "1794", + "pioneers", + "sigma", + "licensing", + "##sus", + "wheelchair", + "polite", + "1807", + "liquor", + "pratt", + "trustee", + "##uta", + "forewings", + "balloon", + "##zz", + "kilometre", + "camping", + "explicit", + "casually", + "shawn", + "foolish", + "teammates", + "nm", + "hassan", + "carrie", + "judged", + "satisfy", + "vanessa", + "knives", + "selective", + "cnn", + "flowed", + "##lice", + "eclipse", + "stressed", + "eliza", + "mathematician", + "cease", + "cultivated", + "##roy", + "commissions", + "browns", + "##ania", + "destroyers", + "sheridan", + "meadow", + "##rius", + "minerals", + "##cial", + "downstream", + "clash", + "gram", + "memoirs", + "ventures", + "baha", + "seymour", + "archie", + "midlands", + "edith", + "fare", + "flynn", + "invite", + "canceled", + "tiles", + "stabbed", + "boulder", + "incorporate", + "amended", + "camden", + "facial", + "mollusk", + "unreleased", + "descriptions", + "yoga", + "grabs", + "550", + "raises", + "ramp", + "shiver", + "##rose", + "coined", + "pioneering", + "tunes", + "qing", + "warwick", + "tops", + "119", + "melanie", + "giles", + "##rous", + "wandered", + "##inal", + "annexed", + "nov", + "30th", + "unnamed", + "##ished", + "organizational", + "airplane", + "normandy", + "stoke", + "whistle", + "blessing", + "violations", + "chased", + "holders", + "shotgun", + "##ctic", + "outlet", + "reactor", + "##vik", + "tires", + "tearing", + "shores", + "fortified", + "mascot", + "constituencies", + "nc", + "columnist", + "productive", + "tibet", + "##rta", + "lineage", + "hooked", + "oct", + "tapes", + "judging", + "cody", + "##gger", + "hansen", + "kashmir", + "triggered", + "##eva", + "solved", + "cliffs", + "##tree", + "resisted", + "anatomy", + "protesters", + "transparent", + "implied", + "##iga", + "injection", + "mattress", + "excluding", + "##mbo", + "defenses", + "helpless", + "devotion", + "##elli", + "growl", + "liberals", + "weber", + "phenomena", + "atoms", + "plug", + "##iff", + "mortality", + "apprentice", + "howe", + "convincing", + "aaa", + "swimmer", + "barber", + "leone", + "promptly", + "sodium", + "def", + "nowadays", + "arise", + "##oning", + "gloucester", + "corrected", + "dignity", + "norm", + "erie", + "##ders", + "elders", + "evacuated", + "sylvia", + "compression", + "##yar", + "hartford", + "pose", + "backpack", + "reasoning", + "accepts", + "24th", + "wipe", + "millimetres", + "marcel", + "##oda", + "dodgers", + "albion", + "1790", + "overwhelmed", + "aerospace", + "oaks", + "1795", + "showcase", + "acknowledge", + "recovering", + "nolan", + "ashe", + "hurts", + "geology", + "fashioned", + "disappearance", + "farewell", + "swollen", + "shrug", + "marquis", + "wimbledon", + "124", + "rue", + "1792", + "commemorate", + "reduces", + "experiencing", + "inevitable", + "calcutta", + "intel", + "##court", + "murderer", + "sticking", + "fisheries", + "imagery", + "bloom", + "280", + "brake", + "##inus", + "gustav", + "hesitation", + "memorable", + "po", + "viral", + "beans", + "accidents", + "tunisia", + "antenna", + "spilled", + "consort", + "treatments", + "aye", + "perimeter", + "##gard", + "donation", + "hostage", + "migrated", + "banker", + "addiction", + "apex", + "lil", + "trout", + "##ously", + "conscience", + "##nova", + "rams", + "sands", + "genome", + "passionate", + "troubles", + "##lets", + "##set", + "amid", + "##ibility", + "##ret", + "higgins", + "exceed", + "vikings", + "##vie", + "payne", + "##zan", + "muscular", + "##ste", + "defendant", + "sucking", + "##wal", + "ibrahim", + "fuselage", + "claudia", + "vfl", + "europeans", + "snails", + "interval", + "##garh", + "preparatory", + "statewide", + "tasked", + "lacrosse", + "viktor", + "##lation", + "angola", + "##hra", + "flint", + "implications", + "employs", + "teens", + "patrons", + "stall", + "weekends", + "barriers", + "scrambled", + "nucleus", + "tehran", + "jenna", + "parsons", + "lifelong", + "robots", + "displacement", + "5000", + "##bles", + "precipitation", + "##gt", + "knuckles", + "clutched", + "1802", + "marrying", + "ecology", + "marx", + "accusations", + "declare", + "scars", + "kolkata", + "mat", + "meadows", + "bermuda", + "skeleton", + "finalists", + "vintage", + "crawl", + "coordinate", + "affects", + "subjected", + "orchestral", + "mistaken", + "##tc", + "mirrors", + "dipped", + "relied", + "260", + "arches", + "candle", + "##nick", + "incorporating", + "wildly", + "fond", + "basilica", + "owl", + "fringe", + "rituals", + "whispering", + "stirred", + "feud", + "tertiary", + "slick", + "goat", + "honorable", + "whereby", + "skip", + "ricardo", + "stripes", + "parachute", + "adjoining", + "submerged", + "synthesizer", + "##gren", + "intend", + "positively", + "ninety", + "phi", + "beaver", + "partition", + "fellows", + "alexis", + "prohibition", + "carlisle", + "bizarre", + "fraternity", + "##bre", + "doubts", + "icy", + "cbc", + "aquatic", + "sneak", + "sonny", + "combines", + "airports", + "crude", + "supervised", + "spatial", + "merge", + "alfonso", + "##bic", + "corrupt", + "scan", + "undergo", + "##ams", + "disabilities", + "colombian", + "comparing", + "dolphins", + "perkins", + "##lish", + "reprinted", + "unanimous", + "bounced", + "hairs", + "underworld", + "midwest", + "semester", + "bucket", + "paperback", + "miniseries", + "coventry", + "demise", + "##leigh", + "demonstrations", + "sensor", + "rotating", + "yan", + "##hler", + "arrange", + "soils", + "##idge", + "hyderabad", + "labs", + "##dr", + "brakes", + "grandchildren", + "##nde", + "negotiated", + "rover", + "ferrari", + "continuation", + "directorate", + "augusta", + "stevenson", + "counterpart", + "gore", + "##rda", + "nursery", + "rican", + "ave", + "collectively", + "broadly", + "pastoral", + "repertoire", + "asserted", + "discovering", + "nordic", + "styled", + "fiba", + "cunningham", + "harley", + "middlesex", + "survives", + "tumor", + "tempo", + "zack", + "aiming", + "lok", + "urgent", + "##rade", + "##nto", + "devils", + "##ement", + "contractor", + "turin", + "##wl", + "##ool", + "bliss", + "repaired", + "simmons", + "moan", + "astronomical", + "cr", + "negotiate", + "lyric", + "1890s", + "lara", + "bred", + "clad", + "angus", + "pbs", + "##ience", + "engineered", + "posed", + "##lk", + "hernandez", + "possessions", + "elbows", + "psychiatric", + "strokes", + "confluence", + "electorate", + "lifts", + "campuses", + "lava", + "alps", + "##ep", + "##ution", + "##date", + "physicist", + "woody", + "##page", + "##ographic", + "##itis", + "juliet", + "reformation", + "sparhawk", + "320", + "complement", + "suppressed", + "jewel", + "##\u00bd", + "floated", + "##kas", + "continuity", + "sadly", + "##ische", + "inability", + "melting", + "scanning", + "paula", + "flour", + "judaism", + "safer", + "vague", + "##lm", + "solving", + "curb", + "##stown", + "financially", + "gable", + "bees", + "expired", + "miserable", + "cassidy", + "dominion", + "1789", + "cupped", + "145", + "robbery", + "facto", + "amos", + "warden", + "resume", + "tallest", + "marvin", + "ing", + "pounded", + "usd", + "declaring", + "gasoline", + "##aux", + "darkened", + "270", + "650", + "sophomore", + "##mere", + "erection", + "gossip", + "televised", + "risen", + "dial", + "##eu", + "pillars", + "##link", + "passages", + "profound", + "##tina", + "arabian", + "ashton", + "silicon", + "nail", + "##ead", + "##lated", + "##wer", + "##hardt", + "fleming", + "firearms", + "ducked", + "circuits", + "blows", + "waterloo", + "titans", + "##lina", + "atom", + "fireplace", + "cheshire", + "financed", + "activation", + "algorithms", + "##zzi", + "constituent", + "catcher", + "cherokee", + "partnerships", + "sexuality", + "platoon", + "tragic", + "vivian", + "guarded", + "whiskey", + "meditation", + "poetic", + "##late", + "##nga", + "##ake", + "porto", + "listeners", + "dominance", + "kendra", + "mona", + "chandler", + "factions", + "22nd", + "salisbury", + "attitudes", + "derivative", + "##ido", + "##haus", + "intake", + "paced", + "javier", + "illustrator", + "barrels", + "bias", + "cockpit", + "burnett", + "dreamed", + "ensuing", + "##anda", + "receptors", + "someday", + "hawkins", + "mattered", + "##lal", + "slavic", + "1799", + "jesuit", + "cameroon", + "wasted", + "tai", + "wax", + "lowering", + "victorious", + "freaking", + "outright", + "hancock", + "librarian", + "sensing", + "bald", + "calcium", + "myers", + "tablet", + "announcing", + "barack", + "shipyard", + "pharmaceutical", + "##uan", + "greenwich", + "flush", + "medley", + "patches", + "wolfgang", + "pt", + "speeches", + "acquiring", + "exams", + "nikolai", + "##gg", + "hayden", + "kannada", + "##type", + "reilly", + "##pt", + "waitress", + "abdomen", + "devastated", + "capped", + "pseudonym", + "pharmacy", + "fulfill", + "paraguay", + "1796", + "clicked", + "##trom", + "archipelago", + "syndicated", + "##hman", + "lumber", + "orgasm", + "rejection", + "clifford", + "lorraine", + "advent", + "mafia", + "rodney", + "brock", + "##ght", + "##used", + "##elia", + "cassette", + "chamberlain", + "despair", + "mongolia", + "sensors", + "developmental", + "upstream", + "##eg", + "##alis", + "spanning", + "165", + "trombone", + "basque", + "seeded", + "interred", + "renewable", + "rhys", + "leapt", + "revision", + "molecule", + "##ages", + "chord", + "vicious", + "nord", + "shivered", + "23rd", + "arlington", + "debts", + "corpus", + "sunrise", + "bays", + "blackburn", + "centimetres", + "##uded", + "shuddered", + "gm", + "strangely", + "gripping", + "cartoons", + "isabelle", + "orbital", + "##ppa", + "seals", + "proving", + "##lton", + "refusal", + "strengthened", + "bust", + "assisting", + "baghdad", + "batsman", + "portrayal", + "mara", + "pushes", + "spears", + "og", + "##cock", + "reside", + "nathaniel", + "brennan", + "1776", + "confirmation", + "caucus", + "##worthy", + "markings", + "yemen", + "nobles", + "ku", + "lazy", + "viewer", + "catalan", + "encompasses", + "sawyer", + "##fall", + "sparked", + "substances", + "patents", + "braves", + "arranger", + "evacuation", + "sergio", + "persuade", + "dover", + "tolerance", + "penguin", + "cum", + "jockey", + "insufficient", + "townships", + "occupying", + "declining", + "plural", + "processed", + "projection", + "puppet", + "flanders", + "introduces", + "liability", + "##yon", + "gymnastics", + "antwerp", + "taipei", + "hobart", + "candles", + "jeep", + "wes", + "observers", + "126", + "chaplain", + "bundle", + "glorious", + "##hine", + "hazel", + "flung", + "sol", + "excavations", + "dumped", + "stares", + "sh", + "bangalore", + "triangular", + "icelandic", + "intervals", + "expressing", + "turbine", + "##vers", + "songwriting", + "crafts", + "##igo", + "jasmine", + "ditch", + "rite", + "##ways", + "entertaining", + "comply", + "sorrow", + "wrestlers", + "basel", + "emirates", + "marian", + "rivera", + "helpful", + "##some", + "caution", + "downward", + "networking", + "##atory", + "##tered", + "darted", + "genocide", + "emergence", + "replies", + "specializing", + "spokesman", + "convenient", + "unlocked", + "fading", + "augustine", + "concentrations", + "resemblance", + "elijah", + "investigator", + "andhra", + "##uda", + "promotes", + "bean", + "##rrell", + "fleeing", + "wan", + "simone", + "announcer", + "##ame", + "##bby", + "lydia", + "weaver", + "132", + "residency", + "modification", + "##fest", + "stretches", + "##ast", + "alternatively", + "nat", + "lowe", + "lacks", + "##ented", + "pam", + "tile", + "concealed", + "inferior", + "abdullah", + "residences", + "tissues", + "vengeance", + "##ided", + "moisture", + "peculiar", + "groove", + "zip", + "bologna", + "jennings", + "ninja", + "oversaw", + "zombies", + "pumping", + "batch", + "livingston", + "emerald", + "installations", + "1797", + "peel", + "nitrogen", + "rama", + "##fying", + "##star", + "schooling", + "strands", + "responding", + "werner", + "##ost", + "lime", + "casa", + "accurately", + "targeting", + "##rod", + "underway", + "##uru", + "hemisphere", + "lester", + "##yard", + "occupies", + "2d", + "griffith", + "angrily", + "reorganized", + "##owing", + "courtney", + "deposited", + "##dd", + "##30", + "estadio", + "##ifies", + "dunn", + "exiled", + "##ying", + "checks", + "##combe", + "##\u043e", + "##fly", + "successes", + "unexpectedly", + "blu", + "assessed", + "##flower", + "##\u0647", + "observing", + "sacked", + "spiders", + "kn", + "##tail", + "mu", + "nodes", + "prosperity", + "audrey", + "divisional", + "155", + "broncos", + "tangled", + "adjust", + "feeds", + "erosion", + "paolo", + "surf", + "directory", + "snatched", + "humid", + "admiralty", + "screwed", + "gt", + "reddish", + "##nese", + "modules", + "trench", + "lamps", + "bind", + "leah", + "bucks", + "competes", + "##nz", + "##form", + "transcription", + "##uc", + "isles", + "violently", + "clutching", + "pga", + "cyclist", + "inflation", + "flats", + "ragged", + "unnecessary", + "##hian", + "stubborn", + "coordinated", + "harriet", + "baba", + "disqualified", + "330", + "insect", + "wolfe", + "##fies", + "reinforcements", + "rocked", + "duel", + "winked", + "embraced", + "bricks", + "##raj", + "hiatus", + "defeats", + "pending", + "brightly", + "jealousy", + "##xton", + "##hm", + "##uki", + "lena", + "gdp", + "colorful", + "##dley", + "stein", + "kidney", + "##shu", + "underwear", + "wanderers", + "##haw", + "##icus", + "guardians", + "m\u00b3", + "roared", + "habits", + "##wise", + "permits", + "gp", + "uranium", + "punished", + "disguise", + "bundesliga", + "elise", + "dundee", + "erotic", + "partisan", + "pi", + "collectors", + "float", + "individually", + "rendering", + "behavioral", + "bucharest", + "ser", + "hare", + "valerie", + "corporal", + "nutrition", + "proportional", + "##isa", + "immense", + "##kis", + "pavement", + "##zie", + "##eld", + "sutherland", + "crouched", + "1775", + "##lp", + "suzuki", + "trades", + "endurance", + "operas", + "crosby", + "prayed", + "priory", + "rory", + "socially", + "##urn", + "gujarat", + "##pu", + "walton", + "cube", + "pasha", + "privilege", + "lennon", + "floods", + "thorne", + "waterfall", + "nipple", + "scouting", + "approve", + "##lov", + "minorities", + "voter", + "dwight", + "extensions", + "assure", + "ballroom", + "slap", + "dripping", + "privileges", + "rejoined", + "confessed", + "demonstrating", + "patriotic", + "yell", + "investor", + "##uth", + "pagan", + "slumped", + "squares", + "##cle", + "##kins", + "confront", + "bert", + "embarrassment", + "##aid", + "aston", + "urging", + "sweater", + "starr", + "yuri", + "brains", + "williamson", + "commuter", + "mortar", + "structured", + "selfish", + "exports", + "##jon", + "cds", + "##him", + "unfinished", + "##rre", + "mortgage", + "destinations", + "##nagar", + "canoe", + "solitary", + "buchanan", + "delays", + "magistrate", + "fk", + "##pling", + "motivation", + "##lier", + "##vier", + "recruiting", + "assess", + "##mouth", + "malik", + "antique", + "1791", + "pius", + "rahman", + "reich", + "tub", + "zhou", + "smashed", + "airs", + "galway", + "xii", + "conditioning", + "honduras", + "discharged", + "dexter", + "##pf", + "lionel", + "129", + "debates", + "lemon", + "tiffany", + "volunteered", + "dom", + "dioxide", + "procession", + "devi", + "sic", + "tremendous", + "advertisements", + "colts", + "transferring", + "verdict", + "hanover", + "decommissioned", + "utter", + "relate", + "pac", + "racism", + "##top", + "beacon", + "limp", + "similarity", + "terra", + "occurrence", + "ant", + "##how", + "becky", + "capt", + "updates", + "armament", + "richie", + "pal", + "##graph", + "halloween", + "mayo", + "##ssen", + "##bone", + "cara", + "serena", + "fcc", + "dolls", + "obligations", + "##dling", + "violated", + "lafayette", + "jakarta", + "exploitation", + "##ime", + "infamous", + "iconic", + "##lah", + "##park", + "kitty", + "moody", + "reginald", + "dread", + "spill", + "crystals", + "olivier", + "modeled", + "bluff", + "equilibrium", + "separating", + "notices", + "ordnance", + "extinction", + "onset", + "cosmic", + "attachment", + "sammy", + "expose", + "privy", + "anchored", + "##bil", + "abbott", + "admits", + "bending", + "baritone", + "emmanuel", + "policeman", + "vaughan", + "winged", + "climax", + "dresses", + "denny", + "polytechnic", + "mohamed", + "burmese", + "authentic", + "nikki", + "genetics", + "grandparents", + "homestead", + "gaza", + "postponed", + "metacritic", + "una", + "##sby", + "##bat", + "unstable", + "dissertation", + "##rial", + "##cian", + "curls", + "obscure", + "uncovered", + "bronx", + "praying", + "disappearing", + "##hoe", + "prehistoric", + "coke", + "turret", + "mutations", + "nonprofit", + "pits", + "monaco", + "##\u064a", + "##usion", + "prominently", + "dispatched", + "podium", + "##mir", + "uci", + "##uation", + "133", + "fortifications", + "birthplace", + "kendall", + "##lby", + "##oll", + "preacher", + "rack", + "goodman", + "##rman", + "persistent", + "##ott", + "countless", + "jaime", + "recorder", + "lexington", + "persecution", + "jumps", + "renewal", + "wagons", + "##11", + "crushing", + "##holder", + "decorations", + "##lake", + "abundance", + "wrath", + "laundry", + "\u00a31", + "garde", + "##rp", + "jeanne", + "beetles", + "peasant", + "##sl", + "splitting", + "caste", + "sergei", + "##rer", + "##ema", + "scripts", + "##ively", + "rub", + "satellites", + "##vor", + "inscribed", + "verlag", + "scrapped", + "gale", + "packages", + "chick", + "potato", + "slogan", + "kathleen", + "arabs", + "##culture", + "counterparts", + "reminiscent", + "choral", + "##tead", + "rand", + "retains", + "bushes", + "dane", + "accomplish", + "courtesy", + "closes", + "##oth", + "slaughter", + "hague", + "krakow", + "lawson", + "tailed", + "elias", + "ginger", + "##ttes", + "canopy", + "betrayal", + "rebuilding", + "turf", + "##hof", + "frowning", + "allegiance", + "brigades", + "kicks", + "rebuild", + "polls", + "alias", + "nationalism", + "td", + "rowan", + "audition", + "bowie", + "fortunately", + "recognizes", + "harp", + "dillon", + "horrified", + "##oro", + "renault", + "##tics", + "ropes", + "##\u03b1", + "presumed", + "rewarded", + "infrared", + "wiping", + "accelerated", + "illustration", + "##rid", + "presses", + "practitioners", + "badminton", + "##iard", + "detained", + "##tera", + "recognizing", + "relates", + "misery", + "##sies", + "##tly", + "reproduction", + "piercing", + "potatoes", + "thornton", + "esther", + "manners", + "hbo", + "##aan", + "ours", + "bullshit", + "ernie", + "perennial", + "sensitivity", + "illuminated", + "rupert", + "##jin", + "##iss", + "##ear", + "rfc", + "nassau", + "##dock", + "staggered", + "socialism", + "##haven", + "appointments", + "nonsense", + "prestige", + "sharma", + "haul", + "##tical", + "solidarity", + "gps", + "##ook", + "##rata", + "igor", + "pedestrian", + "##uit", + "baxter", + "tenants", + "wires", + "medication", + "unlimited", + "guiding", + "impacts", + "diabetes", + "##rama", + "sasha", + "pas", + "clive", + "extraction", + "131", + "continually", + "constraints", + "##bilities", + "sonata", + "hunted", + "sixteenth", + "chu", + "planting", + "quote", + "mayer", + "pretended", + "abs", + "spat", + "##hua", + "ceramic", + "##cci", + "curtains", + "pigs", + "pitching", + "##dad", + "latvian", + "sore", + "dayton", + "##sted", + "##qi", + "patrols", + "slice", + "playground", + "##nted", + "shone", + "stool", + "apparatus", + "inadequate", + "mates", + "treason", + "##ija", + "desires", + "##liga", + "##croft", + "somalia", + "laurent", + "mir", + "leonardo", + "oracle", + "grape", + "obliged", + "chevrolet", + "thirteenth", + "stunning", + "enthusiastic", + "##ede", + "accounted", + "concludes", + "currents", + "basil", + "##kovic", + "drought", + "##rica", + "mai", + "##aire", + "shove", + "posting", + "##shed", + "pilgrimage", + "humorous", + "packing", + "fry", + "pencil", + "wines", + "smells", + "144", + "marilyn", + "aching", + "newest", + "clung", + "bon", + "neighbours", + "sanctioned", + "##pie", + "mug", + "##stock", + "drowning", + "##mma", + "hydraulic", + "##vil", + "hiring", + "reminder", + "lilly", + "investigators", + "##ncies", + "sour", + "##eous", + "compulsory", + "packet", + "##rion", + "##graphic", + "##elle", + "cannes", + "##inate", + "depressed", + "##rit", + "heroic", + "importantly", + "theresa", + "##tled", + "conway", + "saturn", + "marginal", + "rae", + "##xia", + "corresponds", + "royce", + "pact", + "jasper", + "explosives", + "packaging", + "aluminium", + "##ttered", + "denotes", + "rhythmic", + "spans", + "assignments", + "hereditary", + "outlined", + "originating", + "sundays", + "lad", + "reissued", + "greeting", + "beatrice", + "##dic", + "pillar", + "marcos", + "plots", + "handbook", + "alcoholic", + "judiciary", + "avant", + "slides", + "extract", + "masculine", + "blur", + "##eum", + "##force", + "homage", + "trembled", + "owens", + "hymn", + "trey", + "omega", + "signaling", + "socks", + "accumulated", + "reacted", + "attic", + "theo", + "lining", + "angie", + "distraction", + "primera", + "talbot", + "##key", + "1200", + "ti", + "creativity", + "billed", + "##hey", + "deacon", + "eduardo", + "identifies", + "proposition", + "dizzy", + "gunner", + "hogan", + "##yam", + "##pping", + "##hol", + "ja", + "##chan", + "jensen", + "reconstructed", + "##berger", + "clearance", + "darius", + "##nier", + "abe", + "harlem", + "plea", + "dei", + "circled", + "emotionally", + "notation", + "fascist", + "neville", + "exceeded", + "upwards", + "viable", + "ducks", + "##fo", + "workforce", + "racer", + "limiting", + "shri", + "##lson", + "possesses", + "1600", + "kerr", + "moths", + "devastating", + "laden", + "disturbing", + "locking", + "##cture", + "gal", + "fearing", + "accreditation", + "flavor", + "aide", + "1870s", + "mountainous", + "##baum", + "melt", + "##ures", + "motel", + "texture", + "servers", + "soda", + "##mb", + "herd", + "##nium", + "erect", + "puzzled", + "hum", + "peggy", + "examinations", + "gould", + "testified", + "geoff", + "ren", + "devised", + "sacks", + "##law", + "denial", + "posters", + "grunted", + "cesar", + "tutor", + "ec", + "gerry", + "offerings", + "byrne", + "falcons", + "combinations", + "ct", + "incoming", + "pardon", + "rocking", + "26th", + "avengers", + "flared", + "mankind", + "seller", + "uttar", + "loch", + "nadia", + "stroking", + "exposing", + "##hd", + "fertile", + "ancestral", + "instituted", + "##has", + "noises", + "prophecy", + "taxation", + "eminent", + "vivid", + "pol", + "##bol", + "dart", + "indirect", + "multimedia", + "notebook", + "upside", + "displaying", + "adrenaline", + "referenced", + "geometric", + "##iving", + "progression", + "##ddy", + "blunt", + "announce", + "##far", + "implementing", + "##lav", + "aggression", + "liaison", + "cooler", + "cares", + "headache", + "plantations", + "gorge", + "dots", + "impulse", + "thickness", + "ashamed", + "averaging", + "kathy", + "obligation", + "precursor", + "137", + "fowler", + "symmetry", + "thee", + "225", + "hears", + "##rai", + "undergoing", + "ads", + "butcher", + "bowler", + "##lip", + "cigarettes", + "subscription", + "goodness", + "##ically", + "browne", + "##hos", + "##tech", + "kyoto", + "donor", + "##erty", + "damaging", + "friction", + "drifting", + "expeditions", + "hardened", + "prostitution", + "152", + "fauna", + "blankets", + "claw", + "tossing", + "snarled", + "butterflies", + "recruits", + "investigative", + "coated", + "healed", + "138", + "communal", + "hai", + "xiii", + "academics", + "boone", + "psychologist", + "restless", + "lahore", + "stephens", + "mba", + "brendan", + "foreigners", + "printer", + "##pc", + "ached", + "explode", + "27th", + "deed", + "scratched", + "dared", + "##pole", + "cardiac", + "1780", + "okinawa", + "proto", + "commando", + "compelled", + "oddly", + "electrons", + "##base", + "replica", + "thanksgiving", + "##rist", + "sheila", + "deliberate", + "stafford", + "tidal", + "representations", + "hercules", + "ou", + "##path", + "##iated", + "kidnapping", + "lenses", + "##tling", + "deficit", + "samoa", + "mouths", + "consuming", + "computational", + "maze", + "granting", + "smirk", + "razor", + "fixture", + "ideals", + "inviting", + "aiden", + "nominal", + "##vs", + "issuing", + "julio", + "pitt", + "ramsey", + "docks", + "##oss", + "exhaust", + "##owed", + "bavarian", + "draped", + "anterior", + "mating", + "ethiopian", + "explores", + "noticing", + "##nton", + "discarded", + "convenience", + "hoffman", + "endowment", + "beasts", + "cartridge", + "mormon", + "paternal", + "probe", + "sleeves", + "interfere", + "lump", + "deadline", + "##rail", + "jenks", + "bulldogs", + "scrap", + "alternating", + "justified", + "reproductive", + "nam", + "seize", + "descending", + "secretariat", + "kirby", + "coupe", + "grouped", + "smash", + "panther", + "sedan", + "tapping", + "##18", + "lola", + "cheer", + "germanic", + "unfortunate", + "##eter", + "unrelated", + "##fan", + "subordinate", + "##sdale", + "suzanne", + "advertisement", + "##ility", + "horsepower", + "##lda", + "cautiously", + "discourse", + "luigi", + "##mans", + "##fields", + "noun", + "prevalent", + "mao", + "schneider", + "everett", + "surround", + "governorate", + "kira", + "##avia", + "westward", + "##take", + "misty", + "rails", + "sustainability", + "134", + "unused", + "##rating", + "packs", + "toast", + "unwilling", + "regulate", + "thy", + "suffrage", + "nile", + "awe", + "assam", + "definitions", + "travelers", + "affordable", + "##rb", + "conferred", + "sells", + "undefeated", + "beneficial", + "torso", + "basal", + "repeating", + "remixes", + "##pass", + "bahrain", + "cables", + "fang", + "##itated", + "excavated", + "numbering", + "statutory", + "##rey", + "deluxe", + "##lian", + "forested", + "ramirez", + "derbyshire", + "zeus", + "slamming", + "transfers", + "astronomer", + "banana", + "lottery", + "berg", + "histories", + "bamboo", + "##uchi", + "resurrection", + "posterior", + "bowls", + "vaguely", + "##thi", + "thou", + "preserving", + "tensed", + "offence", + "##inas", + "meyrick", + "callum", + "ridden", + "watt", + "langdon", + "tying", + "lowland", + "snorted", + "daring", + "truman", + "##hale", + "##girl", + "aura", + "overly", + "filing", + "weighing", + "goa", + "infections", + "philanthropist", + "saunders", + "eponymous", + "##owski", + "latitude", + "perspectives", + "reviewing", + "mets", + "commandant", + "radial", + "##kha", + "flashlight", + "reliability", + "koch", + "vowels", + "amazed", + "ada", + "elaine", + "supper", + "##rth", + "##encies", + "predator", + "debated", + "soviets", + "cola", + "##boards", + "##nah", + "compartment", + "crooked", + "arbitrary", + "fourteenth", + "##ctive", + "havana", + "majors", + "steelers", + "clips", + "profitable", + "ambush", + "exited", + "packers", + "##tile", + "nude", + "cracks", + "fungi", + "##\u0435", + "limb", + "trousers", + "josie", + "shelby", + "tens", + "frederic", + "##\u03bf\u03c2", + "definite", + "smoothly", + "constellation", + "insult", + "baton", + "discs", + "lingering", + "##nco", + "conclusions", + "lent", + "staging", + "becker", + "grandpa", + "shaky", + "##tron", + "einstein", + "obstacles", + "sk", + "adverse", + "elle", + "economically", + "##moto", + "mccartney", + "thor", + "dismissal", + "motions", + "readings", + "nostrils", + "treatise", + "##pace", + "squeezing", + "evidently", + "prolonged", + "1783", + "venezuelan", + "je", + "marguerite", + "beirut", + "takeover", + "shareholders", + "##vent", + "denise", + "digit", + "airplay", + "norse", + "##bbling", + "imaginary", + "pills", + "hubert", + "blaze", + "vacated", + "eliminating", + "##ello", + "vine", + "mansfield", + "##tty", + "retrospective", + "barrow", + "borne", + "clutch", + "bail", + "forensic", + "weaving", + "##nett", + "##witz", + "desktop", + "citadel", + "promotions", + "worrying", + "dorset", + "ieee", + "subdivided", + "##iating", + "manned", + "expeditionary", + "pickup", + "synod", + "chuckle", + "185", + "barney", + "##rz", + "##ffin", + "functionality", + "karachi", + "litigation", + "meanings", + "uc", + "lick", + "turbo", + "anders", + "##ffed", + "execute", + "curl", + "oppose", + "ankles", + "typhoon", + "##\u062f", + "##ache", + "##asia", + "linguistics", + "compassion", + "pressures", + "grazing", + "perfection", + "##iting", + "immunity", + "monopoly", + "muddy", + "backgrounds", + "136", + "namibia", + "francesca", + "monitors", + "attracting", + "stunt", + "tuition", + "##\u0438\u0438", + "vegetable", + "##mates", + "##quent", + "mgm", + "jen", + "complexes", + "forts", + "##ond", + "cellar", + "bites", + "seventeenth", + "royals", + "flemish", + "failures", + "mast", + "charities", + "##cular", + "peruvian", + "capitals", + "macmillan", + "ipswich", + "outward", + "frigate", + "postgraduate", + "folds", + "employing", + "##ouse", + "concurrently", + "fiery", + "##tai", + "contingent", + "nightmares", + "monumental", + "nicaragua", + "##kowski", + "lizard", + "mal", + "fielding", + "gig", + "reject", + "##pad", + "harding", + "##ipe", + "coastline", + "##cin", + "##nos", + "beethoven", + "humphrey", + "innovations", + "##tam", + "##nge", + "norris", + "doris", + "solicitor", + "huang", + "obey", + "141", + "##lc", + "niagara", + "##tton", + "shelves", + "aug", + "bourbon", + "curry", + "nightclub", + "specifications", + "hilton", + "##ndo", + "centennial", + "dispersed", + "worm", + "neglected", + "briggs", + "sm", + "font", + "kuala", + "uneasy", + "plc", + "##nstein", + "##bound", + "##aking", + "##burgh", + "awaiting", + "pronunciation", + "##bbed", + "##quest", + "eh", + "optimal", + "zhu", + "raped", + "greens", + "presided", + "brenda", + "worries", + "##life", + "venetian", + "marxist", + "turnout", + "##lius", + "refined", + "braced", + "sins", + "grasped", + "sunderland", + "nickel", + "speculated", + "lowell", + "cyrillic", + "communism", + "fundraising", + "resembling", + "colonists", + "mutant", + "freddie", + "usc", + "##mos", + "gratitude", + "##run", + "mural", + "##lous", + "chemist", + "wi", + "reminds", + "28th", + "steals", + "tess", + "pietro", + "##ingen", + "promoter", + "ri", + "microphone", + "honoured", + "rai", + "sant", + "##qui", + "feather", + "##nson", + "burlington", + "kurdish", + "terrorists", + "deborah", + "sickness", + "##wed", + "##eet", + "hazard", + "irritated", + "desperation", + "veil", + "clarity", + "##rik", + "jewels", + "xv", + "##gged", + "##ows", + "##cup", + "berkshire", + "unfair", + "mysteries", + "orchid", + "winced", + "exhaustion", + "renovations", + "stranded", + "obe", + "infinity", + "##nies", + "adapt", + "redevelopment", + "thanked", + "registry", + "olga", + "domingo", + "noir", + "tudor", + "ole", + "##atus", + "commenting", + "behaviors", + "##ais", + "crisp", + "pauline", + "probable", + "stirling", + "wigan", + "##bian", + "paralympics", + "panting", + "surpassed", + "##rew", + "luca", + "barred", + "pony", + "famed", + "##sters", + "cassandra", + "waiter", + "carolyn", + "exported", + "##orted", + "andres", + "destructive", + "deeds", + "jonah", + "castles", + "vacancy", + "suv", + "##glass", + "1788", + "orchard", + "yep", + "famine", + "belarusian", + "sprang", + "##forth", + "skinny", + "##mis", + "administrators", + "rotterdam", + "zambia", + "zhao", + "boiler", + "discoveries", + "##ride", + "##physics", + "lucius", + "disappointing", + "outreach", + "spoon", + "##frame", + "qualifications", + "unanimously", + "enjoys", + "regency", + "##iidae", + "stade", + "realism", + "veterinary", + "rodgers", + "dump", + "alain", + "chestnut", + "castile", + "censorship", + "rumble", + "gibbs", + "##itor", + "communion", + "reggae", + "inactivated", + "logs", + "loads", + "##houses", + "homosexual", + "##iano", + "ale", + "informs", + "##cas", + "phrases", + "plaster", + "linebacker", + "ambrose", + "kaiser", + "fascinated", + "850", + "limerick", + "recruitment", + "forge", + "mastered", + "##nding", + "leinster", + "rooted", + "threaten", + "##strom", + "borneo", + "##hes", + "suggestions", + "scholarships", + "propeller", + "documentaries", + "patronage", + "coats", + "constructing", + "invest", + "neurons", + "comet", + "entirety", + "shouts", + "identities", + "annoying", + "unchanged", + "wary", + "##antly", + "##ogy", + "neat", + "oversight", + "##kos", + "phillies", + "replay", + "constance", + "##kka", + "incarnation", + "humble", + "skies", + "minus", + "##acy", + "smithsonian", + "##chel", + "guerrilla", + "jar", + "cadets", + "##plate", + "surplus", + "audit", + "##aru", + "cracking", + "joanna", + "louisa", + "pacing", + "##lights", + "intentionally", + "##iri", + "diner", + "nwa", + "imprint", + "australians", + "tong", + "unprecedented", + "bunker", + "naive", + "specialists", + "ark", + "nichols", + "railing", + "leaked", + "pedal", + "##uka", + "shrub", + "longing", + "roofs", + "v8", + "captains", + "neural", + "tuned", + "##ntal", + "##jet", + "emission", + "medina", + "frantic", + "codex", + "definitive", + "sid", + "abolition", + "intensified", + "stocks", + "enrique", + "sustain", + "genoa", + "oxide", + "##written", + "clues", + "cha", + "##gers", + "tributaries", + "fragment", + "venom", + "##rity", + "##ente", + "##sca", + "muffled", + "vain", + "sire", + "laos", + "##ingly", + "##hana", + "hastily", + "snapping", + "surfaced", + "sentiment", + "motive", + "##oft", + "contests", + "approximate", + "mesa", + "luckily", + "dinosaur", + "exchanges", + "propelled", + "accord", + "bourne", + "relieve", + "tow", + "masks", + "offended", + "##ues", + "cynthia", + "##mmer", + "rains", + "bartender", + "zinc", + "reviewers", + "lois", + "##sai", + "legged", + "arrogant", + "rafe", + "rosie", + "comprise", + "handicap", + "blockade", + "inlet", + "lagoon", + "copied", + "drilling", + "shelley", + "petals", + "##inian", + "mandarin", + "obsolete", + "##inated", + "onward", + "arguably", + "productivity", + "cindy", + "praising", + "seldom", + "busch", + "discusses", + "raleigh", + "shortage", + "ranged", + "stanton", + "encouragement", + "firstly", + "conceded", + "overs", + "temporal", + "##uke", + "cbe", + "##bos", + "woo", + "certainty", + "pumps", + "##pton", + "stalked", + "##uli", + "lizzie", + "periodic", + "thieves", + "weaker", + "##night", + "gases", + "shoving", + "chooses", + "wc", + "##chemical", + "prompting", + "weights", + "##kill", + "robust", + "flanked", + "sticky", + "hu", + "tuberculosis", + "##eb", + "##eal", + "christchurch", + "resembled", + "wallet", + "reese", + "inappropriate", + "pictured", + "distract", + "fixing", + "fiddle", + "giggled", + "burger", + "heirs", + "hairy", + "mechanic", + "torque", + "apache", + "obsessed", + "chiefly", + "cheng", + "logging", + "##tag", + "extracted", + "meaningful", + "numb", + "##vsky", + "gloucestershire", + "reminding", + "##bay", + "unite", + "##lit", + "breeds", + "diminished", + "clown", + "glove", + "1860s", + "##\u0646", + "##ug", + "archibald", + "focal", + "freelance", + "sliced", + "depiction", + "##yk", + "organism", + "switches", + "sights", + "stray", + "crawling", + "##ril", + "lever", + "leningrad", + "interpretations", + "loops", + "anytime", + "reel", + "alicia", + "delighted", + "##ech", + "inhaled", + "xiv", + "suitcase", + "bernie", + "vega", + "licenses", + "northampton", + "exclusion", + "induction", + "monasteries", + "racecourse", + "homosexuality", + "##right", + "##sfield", + "##rky", + "dimitri", + "michele", + "alternatives", + "ions", + "commentators", + "genuinely", + "objected", + "pork", + "hospitality", + "fencing", + "stephan", + "warships", + "peripheral", + "wit", + "drunken", + "wrinkled", + "quentin", + "spends", + "departing", + "chung", + "numerical", + "spokesperson", + "##zone", + "johannesburg", + "caliber", + "killers", + "##udge", + "assumes", + "neatly", + "demographic", + "abigail", + "bloc", + "##vel", + "mounting", + "##lain", + "bentley", + "slightest", + "xu", + "recipients", + "##jk", + "merlin", + "##writer", + "seniors", + "prisons", + "blinking", + "hindwings", + "flickered", + "kappa", + "##hel", + "80s", + "strengthening", + "appealing", + "brewing", + "gypsy", + "mali", + "lashes", + "hulk", + "unpleasant", + "harassment", + "bio", + "treaties", + "predict", + "instrumentation", + "pulp", + "troupe", + "boiling", + "mantle", + "##ffe", + "ins", + "##vn", + "dividing", + "handles", + "verbs", + "##onal", + "coconut", + "senegal", + "340", + "thorough", + "gum", + "momentarily", + "##sto", + "cocaine", + "panicked", + "destined", + "##turing", + "teatro", + "denying", + "weary", + "captained", + "mans", + "##hawks", + "##code", + "wakefield", + "bollywood", + "thankfully", + "##16", + "cyril", + "##wu", + "amendments", + "##bahn", + "consultation", + "stud", + "reflections", + "kindness", + "1787", + "internally", + "##ovo", + "tex", + "mosaic", + "distribute", + "paddy", + "seeming", + "143", + "##hic", + "piers", + "##15", + "##mura", + "##verse", + "popularly", + "winger", + "kang", + "sentinel", + "mccoy", + "##anza", + "covenant", + "##bag", + "verge", + "fireworks", + "suppress", + "thrilled", + "dominate", + "##jar", + "swansea", + "##60", + "142", + "reconciliation", + "##ndi", + "stiffened", + "cue", + "dorian", + "##uf", + "damascus", + "amor", + "ida", + "foremost", + "##aga", + "porsche", + "unseen", + "dir", + "##had", + "##azi", + "stony", + "lexi", + "melodies", + "##nko", + "angular", + "integer", + "podcast", + "ants", + "inherent", + "jaws", + "justify", + "persona", + "##olved", + "josephine", + "##nr", + "##ressed", + "customary", + "flashes", + "gala", + "cyrus", + "glaring", + "backyard", + "ariel", + "physiology", + "greenland", + "html", + "stir", + "avon", + "atletico", + "finch", + "methodology", + "ked", + "##lent", + "mas", + "catholicism", + "townsend", + "branding", + "quincy", + "fits", + "containers", + "1777", + "ashore", + "aragon", + "##19", + "forearm", + "poisoning", + "##sd", + "adopting", + "conquer", + "grinding", + "amnesty", + "keller", + "finances", + "evaluate", + "forged", + "lankan", + "instincts", + "##uto", + "guam", + "bosnian", + "photographed", + "workplace", + "desirable", + "protector", + "##dog", + "allocation", + "intently", + "encourages", + "willy", + "##sten", + "bodyguard", + "electro", + "brighter", + "##\u03bd", + "bihar", + "##chev", + "lasts", + "opener", + "amphibious", + "sal", + "verde", + "arte", + "##cope", + "captivity", + "vocabulary", + "yields", + "##tted", + "agreeing", + "desmond", + "pioneered", + "##chus", + "strap", + "campaigned", + "railroads", + "##\u043e\u0432\u0438\u0447", + "emblem", + "##dre", + "stormed", + "501", + "##ulous", + "marijuana", + "northumberland", + "##gn", + "##nath", + "bowen", + "landmarks", + "beaumont", + "##qua", + "danube", + "##bler", + "attorneys", + "th", + "ge", + "flyers", + "critique", + "villains", + "cass", + "mutation", + "acc", + "##0s", + "colombo", + "mckay", + "motif", + "sampling", + "concluding", + "syndicate", + "##rell", + "neon", + "stables", + "ds", + "warnings", + "clint", + "mourning", + "wilkinson", + "##tated", + "merrill", + "leopard", + "evenings", + "exhaled", + "emil", + "sonia", + "ezra", + "discrete", + "stove", + "farrell", + "fifteenth", + "prescribed", + "superhero", + "##rier", + "worms", + "helm", + "wren", + "##duction", + "##hc", + "expo", + "##rator", + "hq", + "unfamiliar", + "antony", + "prevents", + "acceleration", + "fiercely", + "mari", + "painfully", + "calculations", + "cheaper", + "ign", + "clifton", + "irvine", + "davenport", + "mozambique", + "##np", + "pierced", + "##evich", + "wonders", + "##wig", + "##cate", + "##iling", + "crusade", + "ware", + "##uel", + "enzymes", + "reasonably", + "mls", + "##coe", + "mater", + "ambition", + "bunny", + "eliot", + "kernel", + "##fin", + "asphalt", + "headmaster", + "torah", + "aden", + "lush", + "pins", + "waived", + "##care", + "##yas", + "joao", + "substrate", + "enforce", + "##grad", + "##ules", + "alvarez", + "selections", + "epidemic", + "tempted", + "##bit", + "bremen", + "translates", + "ensured", + "waterfront", + "29th", + "forrest", + "manny", + "malone", + "kramer", + "reigning", + "cookies", + "simpler", + "absorption", + "205", + "engraved", + "##ffy", + "evaluated", + "1778", + "haze", + "146", + "comforting", + "crossover", + "##abe", + "thorn", + "##rift", + "##imo", + "##pop", + "suppression", + "fatigue", + "cutter", + "##tr", + "201", + "wurttemberg", + "##orf", + "enforced", + "hovering", + "proprietary", + "gb", + "samurai", + "syllable", + "ascent", + "lacey", + "tick", + "lars", + "tractor", + "merchandise", + "rep", + "bouncing", + "defendants", + "##yre", + "huntington", + "##ground", + "##oko", + "standardized", + "##hor", + "##hima", + "assassinated", + "nu", + "predecessors", + "rainy", + "liar", + "assurance", + "lyrical", + "##uga", + "secondly", + "flattened", + "ios", + "parameter", + "undercover", + "##mity", + "bordeaux", + "punish", + "ridges", + "markers", + "exodus", + "inactive", + "hesitate", + "debbie", + "nyc", + "pledge", + "savoy", + "nagar", + "offset", + "organist", + "##tium", + "hesse", + "marin", + "converting", + "##iver", + "diagram", + "propulsion", + "pu", + "validity", + "reverted", + "supportive", + "##dc", + "ministries", + "clans", + "responds", + "proclamation", + "##inae", + "##\u00f8", + "##rea", + "ein", + "pleading", + "patriot", + "sf", + "birch", + "islanders", + "strauss", + "hates", + "##dh", + "brandenburg", + "concession", + "rd", + "##ob", + "1900s", + "killings", + "textbook", + "antiquity", + "cinematography", + "wharf", + "embarrassing", + "setup", + "creed", + "farmland", + "inequality", + "centred", + "signatures", + "fallon", + "370", + "##ingham", + "##uts", + "ceylon", + "gazing", + "directive", + "laurie", + "##tern", + "globally", + "##uated", + "##dent", + "allah", + "excavation", + "threads", + "##cross", + "148", + "frantically", + "icc", + "utilize", + "determines", + "respiratory", + "thoughtful", + "receptions", + "##dicate", + "merging", + "chandra", + "seine", + "147", + "builders", + "builds", + "diagnostic", + "dev", + "visibility", + "goddamn", + "analyses", + "dhaka", + "cho", + "proves", + "chancel", + "concurrent", + "curiously", + "canadians", + "pumped", + "restoring", + "1850s", + "turtles", + "jaguar", + "sinister", + "spinal", + "traction", + "declan", + "vows", + "1784", + "glowed", + "capitalism", + "swirling", + "install", + "universidad", + "##lder", + "##oat", + "soloist", + "##genic", + "##oor", + "coincidence", + "beginnings", + "nissan", + "dip", + "resorts", + "caucasus", + "combustion", + "infectious", + "##eno", + "pigeon", + "serpent", + "##itating", + "conclude", + "masked", + "salad", + "jew", + "##gr", + "surreal", + "toni", + "##wc", + "harmonica", + "151", + "##gins", + "##etic", + "##coat", + "fishermen", + "intending", + "bravery", + "##wave", + "klaus", + "titan", + "wembley", + "taiwanese", + "ransom", + "40th", + "incorrect", + "hussein", + "eyelids", + "jp", + "cooke", + "dramas", + "utilities", + "##etta", + "##print", + "eisenhower", + "principally", + "granada", + "lana", + "##rak", + "openings", + "concord", + "##bl", + "bethany", + "connie", + "morality", + "sega", + "##mons", + "##nard", + "earnings", + "##kara", + "##cine", + "wii", + "communes", + "##rel", + "coma", + "composing", + "softened", + "severed", + "grapes", + "##17", + "nguyen", + "analyzed", + "warlord", + "hubbard", + "heavenly", + "behave", + "slovenian", + "##hit", + "##ony", + "hailed", + "filmmakers", + "trance", + "caldwell", + "skye", + "unrest", + "coward", + "likelihood", + "##aging", + "bern", + "sci", + "taliban", + "honolulu", + "propose", + "##wang", + "1700", + "browser", + "imagining", + "cobra", + "contributes", + "dukes", + "instinctively", + "conan", + "violinist", + "##ores", + "accessories", + "gradual", + "##amp", + "quotes", + "sioux", + "##dating", + "undertake", + "intercepted", + "sparkling", + "compressed", + "139", + "fungus", + "tombs", + "haley", + "imposing", + "rests", + "degradation", + "lincolnshire", + "retailers", + "wetlands", + "tulsa", + "distributor", + "dungeon", + "nun", + "greenhouse", + "convey", + "atlantis", + "aft", + "exits", + "oman", + "dresser", + "lyons", + "##sti", + "joking", + "eddy", + "judgement", + "omitted", + "digits", + "##cts", + "##game", + "juniors", + "##rae", + "cents", + "stricken", + "une", + "##ngo", + "wizards", + "weir", + "breton", + "nan", + "technician", + "fibers", + "liking", + "royalty", + "##cca", + "154", + "persia", + "terribly", + "magician", + "##rable", + "##unt", + "vance", + "cafeteria", + "booker", + "camille", + "warmer", + "##static", + "consume", + "cavern", + "gaps", + "compass", + "contemporaries", + "foyer", + "soothing", + "graveyard", + "maj", + "plunged", + "blush", + "##wear", + "cascade", + "demonstrates", + "ordinance", + "##nov", + "boyle", + "##lana", + "rockefeller", + "shaken", + "banjo", + "izzy", + "##ense", + "breathless", + "vines", + "##32", + "##eman", + "alterations", + "chromosome", + "dwellings", + "feudal", + "mole", + "153", + "catalonia", + "relics", + "tenant", + "mandated", + "##fm", + "fridge", + "hats", + "honesty", + "patented", + "raul", + "heap", + "cruisers", + "accusing", + "enlightenment", + "infants", + "wherein", + "chatham", + "contractors", + "zen", + "affinity", + "hc", + "osborne", + "piston", + "156", + "traps", + "maturity", + "##rana", + "lagos", + "##zal", + "peering", + "##nay", + "attendant", + "dealers", + "protocols", + "subset", + "prospects", + "biographical", + "##cre", + "artery", + "##zers", + "insignia", + "nuns", + "endured", + "##eration", + "recommend", + "schwartz", + "serbs", + "berger", + "cromwell", + "crossroads", + "##ctor", + "enduring", + "clasped", + "grounded", + "##bine", + "marseille", + "twitched", + "abel", + "choke", + "https", + "catalyst", + "moldova", + "italians", + "##tist", + "disastrous", + "wee", + "##oured", + "##nti", + "wwf", + "nope", + "##piration", + "##asa", + "expresses", + "thumbs", + "167", + "##nza", + "coca", + "1781", + "cheating", + "##ption", + "skipped", + "sensory", + "heidelberg", + "spies", + "satan", + "dangers", + "semifinal", + "202", + "bohemia", + "whitish", + "confusing", + "shipbuilding", + "relies", + "surgeons", + "landings", + "ravi", + "baku", + "moor", + "suffix", + "alejandro", + "##yana", + "litre", + "upheld", + "##unk", + "rajasthan", + "##rek", + "coaster", + "insists", + "posture", + "scenarios", + "etienne", + "favoured", + "appoint", + "transgender", + "elephants", + "poked", + "greenwood", + "defences", + "fulfilled", + "militant", + "somali", + "1758", + "chalk", + "potent", + "##ucci", + "migrants", + "wink", + "assistants", + "nos", + "restriction", + "activism", + "niger", + "##ario", + "colon", + "shaun", + "##sat", + "daphne", + "##erated", + "swam", + "congregations", + "reprise", + "considerations", + "magnet", + "playable", + "xvi", + "##\u0440", + "overthrow", + "tobias", + "knob", + "chavez", + "coding", + "##mers", + "propped", + "katrina", + "orient", + "newcomer", + "##suke", + "temperate", + "##pool", + "farmhouse", + "interrogation", + "##vd", + "committing", + "##vert", + "forthcoming", + "strawberry", + "joaquin", + "macau", + "ponds", + "shocking", + "siberia", + "##cellular", + "chant", + "contributors", + "##nant", + "##ologists", + "sped", + "absorb", + "hail", + "1782", + "spared", + "##hore", + "barbados", + "karate", + "opus", + "originates", + "saul", + "##xie", + "evergreen", + "leaped", + "##rock", + "correlation", + "exaggerated", + "weekday", + "unification", + "bump", + "tracing", + "brig", + "afb", + "pathways", + "utilizing", + "##ners", + "mod", + "mb", + "disturbance", + "kneeling", + "##stad", + "##guchi", + "100th", + "pune", + "##thy", + "decreasing", + "168", + "manipulation", + "miriam", + "academia", + "ecosystem", + "occupational", + "rbi", + "##lem", + "rift", + "##14", + "rotary", + "stacked", + "incorporation", + "awakening", + "generators", + "guerrero", + "racist", + "##omy", + "cyber", + "derivatives", + "culminated", + "allie", + "annals", + "panzer", + "sainte", + "wikipedia", + "pops", + "zu", + "austro", + "##vate", + "algerian", + "politely", + "nicholson", + "mornings", + "educate", + "tastes", + "thrill", + "dartmouth", + "##gating", + "db", + "##jee", + "regan", + "differing", + "concentrating", + "choreography", + "divinity", + "##media", + "pledged", + "alexandre", + "routing", + "gregor", + "madeline", + "##idal", + "apocalypse", + "##hora", + "gunfire", + "culminating", + "elves", + "fined", + "liang", + "lam", + "programmed", + "tar", + "guessing", + "transparency", + "gabrielle", + "##gna", + "cancellation", + "flexibility", + "##lining", + "accession", + "shea", + "stronghold", + "nets", + "specializes", + "##rgan", + "abused", + "hasan", + "sgt", + "ling", + "exceeding", + "##\u2084", + "admiration", + "supermarket", + "##ark", + "photographers", + "specialised", + "tilt", + "resonance", + "hmm", + "perfume", + "380", + "sami", + "threatens", + "garland", + "botany", + "guarding", + "boiled", + "greet", + "puppy", + "russo", + "supplier", + "wilmington", + "vibrant", + "vijay", + "##bius", + "paralympic", + "grumbled", + "paige", + "faa", + "licking", + "margins", + "hurricanes", + "##gong", + "fest", + "grenade", + "ripping", + "##uz", + "counseling", + "weigh", + "##sian", + "needles", + "wiltshire", + "edison", + "costly", + "##not", + "fulton", + "tramway", + "redesigned", + "staffordshire", + "cache", + "gasping", + "watkins", + "sleepy", + "candidacy", + "##group", + "monkeys", + "timeline", + "throbbing", + "##bid", + "##sos", + "berth", + "uzbekistan", + "vanderbilt", + "bothering", + "overturned", + "ballots", + "gem", + "##iger", + "sunglasses", + "subscribers", + "hooker", + "compelling", + "ang", + "exceptionally", + "saloon", + "stab", + "##rdi", + "carla", + "terrifying", + "rom", + "##vision", + "coil", + "##oids", + "satisfying", + "vendors", + "31st", + "mackay", + "deities", + "overlooked", + "ambient", + "bahamas", + "felipe", + "olympia", + "whirled", + "botanist", + "advertised", + "tugging", + "##dden", + "disciples", + "morales", + "unionist", + "rites", + "foley", + "morse", + "motives", + "creepy", + "##\u2080", + "soo", + "##sz", + "bargain", + "highness", + "frightening", + "turnpike", + "tory", + "reorganization", + "##cer", + "depict", + "biographer", + "##walk", + "unopposed", + "manifesto", + "##gles", + "institut", + "emile", + "accidental", + "kapoor", + "##dam", + "kilkenny", + "cortex", + "lively", + "##13", + "romanesque", + "jain", + "shan", + "cannons", + "##ood", + "##ske", + "petrol", + "echoing", + "amalgamated", + "disappears", + "cautious", + "proposes", + "sanctions", + "trenton", + "##\u0631", + "flotilla", + "aus", + "contempt", + "tor", + "canary", + "cote", + "theirs", + "##hun", + "conceptual", + "deleted", + "fascinating", + "paso", + "blazing", + "elf", + "honourable", + "hutchinson", + "##eiro", + "##outh", + "##zin", + "surveyor", + "tee", + "amidst", + "wooded", + "reissue", + "intro", + "##ono", + "cobb", + "shelters", + "newsletter", + "hanson", + "brace", + "encoding", + "confiscated", + "dem", + "caravan", + "marino", + "scroll", + "melodic", + "cows", + "imam", + "##adi", + "##aneous", + "northward", + "searches", + "biodiversity", + "cora", + "310", + "roaring", + "##bers", + "connell", + "theologian", + "halo", + "compose", + "pathetic", + "unmarried", + "dynamo", + "##oot", + "az", + "calculation", + "toulouse", + "deserves", + "humour", + "nr", + "forgiveness", + "tam", + "undergone", + "martyr", + "pamela", + "myths", + "whore", + "counselor", + "hicks", + "290", + "heavens", + "battleship", + "electromagnetic", + "##bbs", + "stellar", + "establishments", + "presley", + "hopped", + "##chin", + "temptation", + "90s", + "wills", + "nas", + "##yuan", + "nhs", + "##nya", + "seminars", + "##yev", + "adaptations", + "gong", + "asher", + "lex", + "indicator", + "sikh", + "tobago", + "cites", + "goin", + "##yte", + "satirical", + "##gies", + "characterised", + "correspond", + "bubbles", + "lure", + "participates", + "##vid", + "eruption", + "skate", + "therapeutic", + "1785", + "canals", + "wholesale", + "defaulted", + "sac", + "460", + "petit", + "##zzled", + "virgil", + "leak", + "ravens", + "256", + "portraying", + "##yx", + "ghetto", + "creators", + "dams", + "portray", + "vicente", + "##rington", + "fae", + "namesake", + "bounty", + "##arium", + "joachim", + "##ota", + "##iser", + "aforementioned", + "axle", + "snout", + "depended", + "dismantled", + "reuben", + "480", + "##ibly", + "gallagher", + "##lau", + "##pd", + "earnest", + "##ieu", + "##iary", + "inflicted", + "objections", + "##llar", + "asa", + "gritted", + "##athy", + "jericho", + "##sea", + "##was", + "flick", + "underside", + "ceramics", + "undead", + "substituted", + "195", + "eastward", + "undoubtedly", + "wheeled", + "chimney", + "##iche", + "guinness", + "cb", + "##ager", + "siding", + "##bell", + "traitor", + "baptiste", + "disguised", + "inauguration", + "149", + "tipperary", + "choreographer", + "perched", + "warmed", + "stationary", + "eco", + "##ike", + "##ntes", + "bacterial", + "##aurus", + "flores", + "phosphate", + "##core", + "attacker", + "invaders", + "alvin", + "intersects", + "a1", + "indirectly", + "immigrated", + "businessmen", + "cornelius", + "valves", + "narrated", + "pill", + "sober", + "ul", + "nationale", + "monastic", + "applicants", + "scenery", + "##jack", + "161", + "motifs", + "constitutes", + "cpu", + "##osh", + "jurisdictions", + "sd", + "tuning", + "irritation", + "woven", + "##uddin", + "fertility", + "gao", + "##erie", + "antagonist", + "impatient", + "glacial", + "hides", + "boarded", + "denominations", + "interception", + "##jas", + "cookie", + "nicola", + "##tee", + "algebraic", + "marquess", + "bahn", + "parole", + "buyers", + "bait", + "turbines", + "paperwork", + "bestowed", + "natasha", + "renee", + "oceans", + "purchases", + "157", + "vaccine", + "215", + "##tock", + "fixtures", + "playhouse", + "integrate", + "jai", + "oswald", + "intellectuals", + "##cky", + "booked", + "nests", + "mortimer", + "##isi", + "obsession", + "sept", + "##gler", + "##sum", + "440", + "scrutiny", + "simultaneous", + "squinted", + "##shin", + "collects", + "oven", + "shankar", + "penned", + "remarkably", + "##\u044f", + "slips", + "luggage", + "spectral", + "1786", + "collaborations", + "louie", + "consolidation", + "##ailed", + "##ivating", + "420", + "hoover", + "blackpool", + "harness", + "ignition", + "vest", + "tails", + "belmont", + "mongol", + "skinner", + "##nae", + "visually", + "mage", + "derry", + "##tism", + "##unce", + "stevie", + "transitional", + "##rdy", + "redskins", + "drying", + "prep", + "prospective", + "##21", + "annoyance", + "oversee", + "##loaded", + "fills", + "##books", + "##iki", + "announces", + "fda", + "scowled", + "respects", + "prasad", + "mystic", + "tucson", + "##vale", + "revue", + "springer", + "bankrupt", + "1772", + "aristotle", + "salvatore", + "habsburg", + "##geny", + "dal", + "natal", + "nut", + "pod", + "chewing", + "darts", + "moroccan", + "walkover", + "rosario", + "lenin", + "punjabi", + "##\u00dfe", + "grossed", + "scattering", + "wired", + "invasive", + "hui", + "polynomial", + "corridors", + "wakes", + "gina", + "portrays", + "##cratic", + "arid", + "retreating", + "erich", + "irwin", + "sniper", + "##dha", + "linen", + "lindsey", + "maneuver", + "butch", + "shutting", + "socio", + "bounce", + "commemorative", + "postseason", + "jeremiah", + "pines", + "275", + "mystical", + "beads", + "bp", + "abbas", + "furnace", + "bidding", + "consulted", + "assaulted", + "empirical", + "rubble", + "enclosure", + "sob", + "weakly", + "cancel", + "polly", + "yielded", + "##emann", + "curly", + "prediction", + "battered", + "70s", + "vhs", + "jacqueline", + "render", + "sails", + "barked", + "detailing", + "grayson", + "riga", + "sloane", + "raging", + "##yah", + "herbs", + "bravo", + "##athlon", + "alloy", + "giggle", + "imminent", + "suffers", + "assumptions", + "waltz", + "##itate", + "accomplishments", + "##ited", + "bathing", + "remixed", + "deception", + "prefix", + "##emia", + "deepest", + "##tier", + "##eis", + "balkan", + "frogs", + "##rong", + "slab", + "##pate", + "philosophers", + "peterborough", + "grains", + "imports", + "dickinson", + "rwanda", + "##atics", + "1774", + "dirk", + "lan", + "tablets", + "##rove", + "clone", + "##rice", + "caretaker", + "hostilities", + "mclean", + "##gre", + "regimental", + "treasures", + "norms", + "impose", + "tsar", + "tango", + "diplomacy", + "variously", + "complain", + "192", + "recognise", + "arrests", + "1779", + "celestial", + "pulitzer", + "##dus", + "bing", + "libretto", + "##moor", + "adele", + "splash", + "##rite", + "expectation", + "lds", + "confronts", + "##izer", + "spontaneous", + "harmful", + "wedge", + "entrepreneurs", + "buyer", + "##ope", + "bilingual", + "translate", + "rugged", + "conner", + "circulated", + "uae", + "eaton", + "##gra", + "##zzle", + "lingered", + "lockheed", + "vishnu", + "reelection", + "alonso", + "##oom", + "joints", + "yankee", + "headline", + "cooperate", + "heinz", + "laureate", + "invading", + "##sford", + "echoes", + "scandinavian", + "##dham", + "hugging", + "vitamin", + "salute", + "micah", + "hind", + "trader", + "##sper", + "radioactive", + "##ndra", + "militants", + "poisoned", + "ratified", + "remark", + "campeonato", + "deprived", + "wander", + "prop", + "##dong", + "outlook", + "##tani", + "##rix", + "##eye", + "chiang", + "darcy", + "##oping", + "mandolin", + "spice", + "statesman", + "babylon", + "182", + "walled", + "forgetting", + "afro", + "##cap", + "158", + "giorgio", + "buffer", + "##polis", + "planetary", + "##gis", + "overlap", + "terminals", + "kinda", + "centenary", + "##bir", + "arising", + "manipulate", + "elm", + "ke", + "1770", + "ak", + "##tad", + "chrysler", + "mapped", + "moose", + "pomeranian", + "quad", + "macarthur", + "assemblies", + "shoreline", + "recalls", + "stratford", + "##rted", + "noticeable", + "##evic", + "imp", + "##rita", + "##sque", + "accustomed", + "supplying", + "tents", + "disgusted", + "vogue", + "sipped", + "filters", + "khz", + "reno", + "selecting", + "luftwaffe", + "mcmahon", + "tyne", + "masterpiece", + "carriages", + "collided", + "dunes", + "exercised", + "flare", + "remembers", + "muzzle", + "##mobile", + "heck", + "##rson", + "burgess", + "lunged", + "middleton", + "boycott", + "bilateral", + "##sity", + "hazardous", + "lumpur", + "multiplayer", + "spotlight", + "jackets", + "goldman", + "liege", + "porcelain", + "rag", + "waterford", + "benz", + "attracts", + "hopeful", + "battling", + "ottomans", + "kensington", + "baked", + "hymns", + "cheyenne", + "lattice", + "levine", + "borrow", + "polymer", + "clashes", + "michaels", + "monitored", + "commitments", + "denounced", + "##25", + "##von", + "cavity", + "##oney", + "hobby", + "akin", + "##holders", + "futures", + "intricate", + "cornish", + "patty", + "##oned", + "illegally", + "dolphin", + "##lag", + "barlow", + "yellowish", + "maddie", + "apologized", + "luton", + "plagued", + "##puram", + "nana", + "##rds", + "sway", + "fanny", + "\u0142odz", + "##rino", + "psi", + "suspicions", + "hanged", + "##eding", + "initiate", + "charlton", + "##por", + "nak", + "competent", + "235", + "analytical", + "annex", + "wardrobe", + "reservations", + "##rma", + "sect", + "162", + "fairfax", + "hedge", + "piled", + "buckingham", + "uneven", + "bauer", + "simplicity", + "snyder", + "interpret", + "accountability", + "donors", + "moderately", + "byrd", + "continents", + "##cite", + "##max", + "disciple", + "hr", + "jamaican", + "ping", + "nominees", + "##uss", + "mongolian", + "diver", + "attackers", + "eagerly", + "ideological", + "pillows", + "miracles", + "apartheid", + "revolver", + "sulfur", + "clinics", + "moran", + "163", + "##enko", + "ile", + "katy", + "rhetoric", + "##icated", + "chronology", + "recycling", + "##hrer", + "elongated", + "mughal", + "pascal", + "profiles", + "vibration", + "databases", + "domination", + "##fare", + "##rant", + "matthias", + "digest", + "rehearsal", + "polling", + "weiss", + "initiation", + "reeves", + "clinging", + "flourished", + "impress", + "ngo", + "##hoff", + "##ume", + "buckley", + "symposium", + "rhythms", + "weed", + "emphasize", + "transforming", + "##taking", + "##gence", + "##yman", + "accountant", + "analyze", + "flicker", + "foil", + "priesthood", + "voluntarily", + "decreases", + "##80", + "##hya", + "slater", + "sv", + "charting", + "mcgill", + "##lde", + "moreno", + "##iu", + "besieged", + "zur", + "robes", + "##phic", + "admitting", + "api", + "deported", + "turmoil", + "peyton", + "earthquakes", + "##ares", + "nationalists", + "beau", + "clair", + "brethren", + "interrupt", + "welch", + "curated", + "galerie", + "requesting", + "164", + "##ested", + "impending", + "steward", + "viper", + "##vina", + "complaining", + "beautifully", + "brandy", + "foam", + "nl", + "1660", + "##cake", + "alessandro", + "punches", + "laced", + "explanations", + "##lim", + "attribute", + "clit", + "reggie", + "discomfort", + "##cards", + "smoothed", + "whales", + "##cene", + "adler", + "countered", + "duffy", + "disciplinary", + "widening", + "recipe", + "reliance", + "conducts", + "goats", + "gradient", + "preaching", + "##shaw", + "matilda", + "quasi", + "striped", + "meridian", + "cannabis", + "cordoba", + "certificates", + "##agh", + "##tering", + "graffiti", + "hangs", + "pilgrims", + "repeats", + "##ych", + "revive", + "urine", + "etat", + "##hawk", + "fueled", + "belts", + "fuzzy", + "susceptible", + "##hang", + "mauritius", + "salle", + "sincere", + "beers", + "hooks", + "##cki", + "arbitration", + "entrusted", + "advise", + "sniffed", + "seminar", + "junk", + "donnell", + "processors", + "principality", + "strapped", + "celia", + "mendoza", + "everton", + "fortunes", + "prejudice", + "starving", + "reassigned", + "steamer", + "##lund", + "tuck", + "evenly", + "foreman", + "##ffen", + "dans", + "375", + "envisioned", + "slit", + "##xy", + "baseman", + "liberia", + "rosemary", + "##weed", + "electrified", + "periodically", + "potassium", + "stride", + "contexts", + "sperm", + "slade", + "mariners", + "influx", + "bianca", + "subcommittee", + "##rane", + "spilling", + "icao", + "estuary", + "##nock", + "delivers", + "iphone", + "##ulata", + "isa", + "mira", + "bohemian", + "dessert", + "##sbury", + "welcoming", + "proudly", + "slowing", + "##chs", + "musee", + "ascension", + "russ", + "##vian", + "waits", + "##psy", + "africans", + "exploit", + "##morphic", + "gov", + "eccentric", + "crab", + "peck", + "##ull", + "entrances", + "formidable", + "marketplace", + "groom", + "bolted", + "metabolism", + "patton", + "robbins", + "courier", + "payload", + "endure", + "##ifier", + "andes", + "refrigerator", + "##pr", + "ornate", + "##uca", + "ruthless", + "illegitimate", + "masonry", + "strasbourg", + "bikes", + "adobe", + "##\u00b3", + "apples", + "quintet", + "willingly", + "niche", + "bakery", + "corpses", + "energetic", + "##cliffe", + "##sser", + "##ards", + "177", + "centimeters", + "centro", + "fuscous", + "cretaceous", + "rancho", + "##yde", + "andrei", + "telecom", + "tottenham", + "oasis", + "ordination", + "vulnerability", + "presiding", + "corey", + "cp", + "penguins", + "sims", + "##pis", + "malawi", + "piss", + "##48", + "correction", + "##cked", + "##ffle", + "##ryn", + "countdown", + "detectives", + "psychiatrist", + "psychedelic", + "dinosaurs", + "blouse", + "##get", + "choi", + "vowed", + "##oz", + "randomly", + "##pol", + "49ers", + "scrub", + "blanche", + "bruins", + "dusseldorf", + "##using", + "unwanted", + "##ums", + "212", + "dominique", + "elevations", + "headlights", + "om", + "laguna", + "##oga", + "1750", + "famously", + "ignorance", + "shrewsbury", + "##aine", + "ajax", + "breuning", + "che", + "confederacy", + "greco", + "overhaul", + "##screen", + "paz", + "skirts", + "disagreement", + "cruelty", + "jagged", + "phoebe", + "shifter", + "hovered", + "viruses", + "##wes", + "mandy", + "##lined", + "##gc", + "landlord", + "squirrel", + "dashed", + "##\u03b9", + "ornamental", + "gag", + "wally", + "grange", + "literal", + "spurs", + "undisclosed", + "proceeding", + "yin", + "##text", + "billie", + "orphan", + "spanned", + "humidity", + "indy", + "weighted", + "presentations", + "explosions", + "lucian", + "##tary", + "vaughn", + "hindus", + "##anga", + "##hell", + "psycho", + "171", + "daytona", + "protects", + "efficiently", + "rematch", + "sly", + "tandem", + "##oya", + "rebranded", + "impaired", + "hee", + "metropolis", + "peach", + "godfrey", + "diaspora", + "ethnicity", + "prosperous", + "gleaming", + "dar", + "grossing", + "playback", + "##rden", + "stripe", + "pistols", + "##tain", + "births", + "labelled", + "##cating", + "172", + "rudy", + "alba", + "##onne", + "aquarium", + "hostility", + "##gb", + "##tase", + "shudder", + "sumatra", + "hardest", + "lakers", + "consonant", + "creeping", + "demos", + "homicide", + "capsule", + "zeke", + "liberties", + "expulsion", + "pueblo", + "##comb", + "trait", + "transporting", + "##ddin", + "##neck", + "##yna", + "depart", + "gregg", + "mold", + "ledge", + "hangar", + "oldham", + "playboy", + "termination", + "analysts", + "gmbh", + "romero", + "##itic", + "insist", + "cradle", + "filthy", + "brightness", + "slash", + "shootout", + "deposed", + "bordering", + "##truct", + "isis", + "microwave", + "tumbled", + "sheltered", + "cathy", + "werewolves", + "messy", + "andersen", + "convex", + "clapped", + "clinched", + "satire", + "wasting", + "edo", + "vc", + "rufus", + "##jak", + "mont", + "##etti", + "poznan", + "##keeping", + "restructuring", + "transverse", + "##rland", + "azerbaijani", + "slovene", + "gestures", + "roommate", + "choking", + "shear", + "##quist", + "vanguard", + "oblivious", + "##hiro", + "disagreed", + "baptism", + "##lich", + "coliseum", + "##aceae", + "salvage", + "societe", + "cory", + "locke", + "relocation", + "relying", + "versailles", + "ahl", + "swelling", + "##elo", + "cheerful", + "##word", + "##edes", + "gin", + "sarajevo", + "obstacle", + "diverted", + "##nac", + "messed", + "thoroughbred", + "fluttered", + "utrecht", + "chewed", + "acquaintance", + "assassins", + "dispatch", + "mirza", + "##wart", + "nike", + "salzburg", + "swell", + "yen", + "##gee", + "idle", + "ligue", + "samson", + "##nds", + "##igh", + "playful", + "spawned", + "##cise", + "tease", + "##case", + "burgundy", + "##bot", + "stirring", + "skeptical", + "interceptions", + "marathi", + "##dies", + "bedrooms", + "aroused", + "pinch", + "##lik", + "preferences", + "tattoos", + "buster", + "digitally", + "projecting", + "rust", + "##ital", + "kitten", + "priorities", + "addison", + "pseudo", + "##guard", + "dusk", + "icons", + "sermon", + "##psis", + "##iba", + "bt", + "##lift", + "##xt", + "ju", + "truce", + "rink", + "##dah", + "##wy", + "defects", + "psychiatry", + "offences", + "calculate", + "glucose", + "##iful", + "##rized", + "##unda", + "francaise", + "##hari", + "richest", + "warwickshire", + "carly", + "1763", + "purity", + "redemption", + "lending", + "##cious", + "muse", + "bruises", + "cerebral", + "aero", + "carving", + "##name", + "preface", + "terminology", + "invade", + "monty", + "##int", + "anarchist", + "blurred", + "##iled", + "rossi", + "treats", + "guts", + "shu", + "foothills", + "ballads", + "undertaking", + "premise", + "cecilia", + "affiliates", + "blasted", + "conditional", + "wilder", + "minors", + "drone", + "rudolph", + "buffy", + "swallowing", + "horton", + "attested", + "##hop", + "rutherford", + "howell", + "primetime", + "livery", + "penal", + "##bis", + "minimize", + "hydro", + "wrecked", + "wrought", + "palazzo", + "##gling", + "cans", + "vernacular", + "friedman", + "nobleman", + "shale", + "walnut", + "danielle", + "##ection", + "##tley", + "sears", + "##kumar", + "chords", + "lend", + "flipping", + "streamed", + "por", + "dracula", + "gallons", + "sacrifices", + "gamble", + "orphanage", + "##iman", + "mckenzie", + "##gible", + "boxers", + "daly", + "##balls", + "##\u0627\u0646", + "208", + "##ific", + "##rative", + "##iq", + "exploited", + "slated", + "##uity", + "circling", + "hillary", + "pinched", + "goldberg", + "provost", + "campaigning", + "lim", + "piles", + "ironically", + "jong", + "mohan", + "successors", + "usaf", + "##tem", + "##ught", + "autobiographical", + "haute", + "preserves", + "##ending", + "acquitted", + "comparisons", + "203", + "hydroelectric", + "gangs", + "cypriot", + "torpedoes", + "rushes", + "chrome", + "derive", + "bumps", + "instability", + "fiat", + "pets", + "##mbe", + "silas", + "dye", + "reckless", + "settler", + "##itation", + "info", + "heats", + "##writing", + "176", + "canonical", + "maltese", + "fins", + "mushroom", + "stacy", + "aspen", + "avid", + "##kur", + "##loading", + "vickers", + "gaston", + "hillside", + "statutes", + "wilde", + "gail", + "kung", + "sabine", + "comfortably", + "motorcycles", + "##rgo", + "169", + "pneumonia", + "fetch", + "##sonic", + "axel", + "faintly", + "parallels", + "##oop", + "mclaren", + "spouse", + "compton", + "interdisciplinary", + "miner", + "##eni", + "181", + "clamped", + "##chal", + "##llah", + "separates", + "versa", + "##mler", + "scarborough", + "labrador", + "##lity", + "##osing", + "rutgers", + "hurdles", + "como", + "166", + "burt", + "divers", + "##100", + "wichita", + "cade", + "coincided", + "##erson", + "bruised", + "mla", + "##pper", + "vineyard", + "##ili", + "##brush", + "notch", + "mentioning", + "jase", + "hearted", + "kits", + "doe", + "##acle", + "pomerania", + "##ady", + "ronan", + "seizure", + "pavel", + "problematic", + "##zaki", + "domenico", + "##ulin", + "catering", + "penelope", + "dependence", + "parental", + "emilio", + "ministerial", + "atkinson", + "##bolic", + "clarkson", + "chargers", + "colby", + "grill", + "peeked", + "arises", + "summon", + "##aged", + "fools", + "##grapher", + "faculties", + "qaeda", + "##vial", + "garner", + "refurbished", + "##hwa", + "geelong", + "disasters", + "nudged", + "bs", + "shareholder", + "lori", + "algae", + "reinstated", + "rot", + "##ades", + "##nous", + "invites", + "stainless", + "183", + "inclusive", + "##itude", + "diocesan", + "til", + "##icz", + "denomination", + "##xa", + "benton", + "floral", + "registers", + "##ider", + "##erman", + "##kell", + "absurd", + "brunei", + "guangzhou", + "hitter", + "retaliation", + "##uled", + "##eve", + "blanc", + "nh", + "consistency", + "contamination", + "##eres", + "##rner", + "dire", + "palermo", + "broadcasters", + "diaries", + "inspire", + "vols", + "brewer", + "tightening", + "ky", + "mixtape", + "hormone", + "##tok", + "stokes", + "##color", + "##dly", + "##ssi", + "pg", + "##ometer", + "##lington", + "sanitation", + "##tility", + "intercontinental", + "apps", + "##adt", + "\u00b9\u2044\u2082", + "cylinders", + "economies", + "favourable", + "unison", + "croix", + "gertrude", + "odyssey", + "vanity", + "dangling", + "##logists", + "upgrades", + "dice", + "middleweight", + "practitioner", + "##ight", + "206", + "henrik", + "parlor", + "orion", + "angered", + "lac", + "python", + "blurted", + "##rri", + "sensual", + "intends", + "swings", + "angled", + "##phs", + "husky", + "attain", + "peerage", + "precinct", + "textiles", + "cheltenham", + "shuffled", + "dai", + "confess", + "tasting", + "bhutan", + "##riation", + "tyrone", + "segregation", + "abrupt", + "ruiz", + "##rish", + "smirked", + "blackwell", + "confidential", + "browning", + "amounted", + "##put", + "vase", + "scarce", + "fabulous", + "raided", + "staple", + "guyana", + "unemployed", + "glider", + "shay", + "##tow", + "carmine", + "troll", + "intervene", + "squash", + "superstar", + "##uce", + "cylindrical", + "len", + "roadway", + "researched", + "handy", + "##rium", + "##jana", + "meta", + "lao", + "declares", + "##rring", + "##tadt", + "##elin", + "##kova", + "willem", + "shrubs", + "napoleonic", + "realms", + "skater", + "qi", + "volkswagen", + "##\u0142", + "tad", + "hara", + "archaeologist", + "awkwardly", + "eerie", + "##kind", + "wiley", + "##heimer", + "##24", + "titus", + "organizers", + "cfl", + "crusaders", + "lama", + "usb", + "vent", + "enraged", + "thankful", + "occupants", + "maximilian", + "##gaard", + "possessing", + "textbooks", + "##oran", + "collaborator", + "quaker", + "##ulo", + "avalanche", + "mono", + "silky", + "straits", + "isaiah", + "mustang", + "surged", + "resolutions", + "potomac", + "descend", + "cl", + "kilograms", + "plato", + "strains", + "saturdays", + "##olin", + "bernstein", + "##ype", + "holstein", + "ponytail", + "##watch", + "belize", + "conversely", + "heroine", + "perpetual", + "##ylus", + "charcoal", + "piedmont", + "glee", + "negotiating", + "backdrop", + "prologue", + "##jah", + "##mmy", + "pasadena", + "climbs", + "ramos", + "sunni", + "##holm", + "##tner", + "##tri", + "anand", + "deficiency", + "hertfordshire", + "stout", + "##avi", + "aperture", + "orioles", + "##irs", + "doncaster", + "intrigued", + "bombed", + "coating", + "otis", + "##mat", + "cocktail", + "##jit", + "##eto", + "amir", + "arousal", + "sar", + "##proof", + "##act", + "##ories", + "dixie", + "pots", + "##bow", + "whereabouts", + "159", + "##fted", + "drains", + "bullying", + "cottages", + "scripture", + "coherent", + "fore", + "poe", + "appetite", + "##uration", + "sampled", + "##ators", + "##dp", + "derrick", + "rotor", + "jays", + "peacock", + "installment", + "##rro", + "advisors", + "##coming", + "rodeo", + "scotch", + "##mot", + "##db", + "##fen", + "##vant", + "ensued", + "rodrigo", + "dictatorship", + "martyrs", + "twenties", + "##\u043d", + "towed", + "incidence", + "marta", + "rainforest", + "sai", + "scaled", + "##cles", + "oceanic", + "qualifiers", + "symphonic", + "mcbride", + "dislike", + "generalized", + "aubrey", + "colonization", + "##iation", + "##lion", + "##ssing", + "disliked", + "lublin", + "salesman", + "##ulates", + "spherical", + "whatsoever", + "sweating", + "avalon", + "contention", + "punt", + "severity", + "alderman", + "atari", + "##dina", + "##grant", + "##rop", + "scarf", + "seville", + "vertices", + "annexation", + "fairfield", + "fascination", + "inspiring", + "launches", + "palatinate", + "regretted", + "##rca", + "feral", + "##iom", + "elk", + "nap", + "olsen", + "reddy", + "yong", + "##leader", + "##iae", + "garment", + "transports", + "feng", + "gracie", + "outrage", + "viceroy", + "insides", + "##esis", + "breakup", + "grady", + "organizer", + "softer", + "grimaced", + "222", + "murals", + "galicia", + "arranging", + "vectors", + "##rsten", + "bas", + "##sb", + "##cens", + "sloan", + "##eka", + "bitten", + "ara", + "fender", + "nausea", + "bumped", + "kris", + "banquet", + "comrades", + "detector", + "persisted", + "##llan", + "adjustment", + "endowed", + "cinemas", + "##shot", + "sellers", + "##uman", + "peek", + "epa", + "kindly", + "neglect", + "simpsons", + "talon", + "mausoleum", + "runaway", + "hangul", + "lookout", + "##cic", + "rewards", + "coughed", + "acquainted", + "chloride", + "##ald", + "quicker", + "accordion", + "neolithic", + "##qa", + "artemis", + "coefficient", + "lenny", + "pandora", + "tx", + "##xed", + "ecstasy", + "litter", + "segunda", + "chairperson", + "gemma", + "hiss", + "rumor", + "vow", + "nasal", + "antioch", + "compensate", + "patiently", + "transformers", + "##eded", + "judo", + "morrow", + "penis", + "posthumous", + "philips", + "bandits", + "husbands", + "denote", + "flaming", + "##any", + "##phones", + "langley", + "yorker", + "1760", + "walters", + "##uo", + "##kle", + "gubernatorial", + "fatty", + "samsung", + "leroy", + "outlaw", + "##nine", + "unpublished", + "poole", + "jakob", + "##\u1d62", + "##\u2099", + "crete", + "distorted", + "superiority", + "##dhi", + "intercept", + "crust", + "mig", + "claus", + "crashes", + "positioning", + "188", + "stallion", + "301", + "frontal", + "armistice", + "##estinal", + "elton", + "aj", + "encompassing", + "camel", + "commemorated", + "malaria", + "woodward", + "calf", + "cigar", + "penetrate", + "##oso", + "willard", + "##rno", + "##uche", + "illustrate", + "amusing", + "convergence", + "noteworthy", + "##lma", + "##rva", + "journeys", + "realise", + "manfred", + "##sable", + "410", + "##vocation", + "hearings", + "fiance", + "##posed", + "educators", + "provoked", + "adjusting", + "##cturing", + "modular", + "stockton", + "paterson", + "vlad", + "rejects", + "electors", + "selena", + "maureen", + "##tres", + "uber", + "##rce", + "swirled", + "##num", + "proportions", + "nanny", + "pawn", + "naturalist", + "parma", + "apostles", + "awoke", + "ethel", + "wen", + "##bey", + "monsoon", + "overview", + "##inating", + "mccain", + "rendition", + "risky", + "adorned", + "##ih", + "equestrian", + "germain", + "nj", + "conspicuous", + "confirming", + "##yoshi", + "shivering", + "##imeter", + "milestone", + "rumours", + "flinched", + "bounds", + "smacked", + "token", + "##bei", + "lectured", + "automobiles", + "##shore", + "impacted", + "##iable", + "nouns", + "nero", + "##leaf", + "ismail", + "prostitute", + "trams", + "##lace", + "bridget", + "sud", + "stimulus", + "impressions", + "reins", + "revolves", + "##oud", + "##gned", + "giro", + "honeymoon", + "##swell", + "criterion", + "##sms", + "##uil", + "libyan", + "prefers", + "##osition", + "211", + "preview", + "sucks", + "accusation", + "bursts", + "metaphor", + "diffusion", + "tolerate", + "faye", + "betting", + "cinematographer", + "liturgical", + "specials", + "bitterly", + "humboldt", + "##ckle", + "flux", + "rattled", + "##itzer", + "archaeologists", + "odor", + "authorised", + "marshes", + "discretion", + "##\u043e\u0432", + "alarmed", + "archaic", + "inverse", + "##leton", + "explorers", + "##pine", + "drummond", + "tsunami", + "woodlands", + "##minate", + "##tland", + "booklet", + "insanity", + "owning", + "insert", + "crafted", + "calculus", + "##tore", + "receivers", + "##bt", + "stung", + "##eca", + "##nched", + "prevailing", + "travellers", + "eyeing", + "lila", + "graphs", + "##borne", + "178", + "julien", + "##won", + "morale", + "adaptive", + "therapist", + "erica", + "cw", + "libertarian", + "bowman", + "pitches", + "vita", + "##ional", + "crook", + "##ads", + "##entation", + "caledonia", + "mutiny", + "##sible", + "1840s", + "automation", + "##\u00df", + "flock", + "##pia", + "ironic", + "pathology", + "##imus", + "remarried", + "##22", + "joker", + "withstand", + "energies", + "##att", + "shropshire", + "hostages", + "madeleine", + "tentatively", + "conflicting", + "mateo", + "recipes", + "euros", + "ol", + "mercenaries", + "nico", + "##ndon", + "albuquerque", + "augmented", + "mythical", + "bel", + "freud", + "##child", + "cough", + "##lica", + "365", + "freddy", + "lillian", + "genetically", + "nuremberg", + "calder", + "209", + "bonn", + "outdoors", + "paste", + "suns", + "urgency", + "vin", + "restraint", + "tyson", + "##cera", + "##selle", + "barrage", + "bethlehem", + "kahn", + "##par", + "mounts", + "nippon", + "barony", + "happier", + "ryu", + "makeshift", + "sheldon", + "blushed", + "castillo", + "barking", + "listener", + "taped", + "bethel", + "fluent", + "headlines", + "pornography", + "rum", + "disclosure", + "sighing", + "mace", + "doubling", + "gunther", + "manly", + "##plex", + "rt", + "interventions", + "physiological", + "forwards", + "emerges", + "##tooth", + "##gny", + "compliment", + "rib", + "recession", + "visibly", + "barge", + "faults", + "connector", + "exquisite", + "prefect", + "##rlin", + "patio", + "##cured", + "elevators", + "brandt", + "italics", + "pena", + "173", + "wasp", + "satin", + "ea", + "botswana", + "graceful", + "respectable", + "##jima", + "##rter", + "##oic", + "franciscan", + "generates", + "##dl", + "alfredo", + "disgusting", + "##olate", + "##iously", + "sherwood", + "warns", + "cod", + "promo", + "cheryl", + "sino", + "##\u0629", + "##escu", + "twitch", + "##zhi", + "brownish", + "thom", + "ortiz", + "##dron", + "densely", + "##beat", + "carmel", + "reinforce", + "##bana", + "187", + "anastasia", + "downhill", + "vertex", + "contaminated", + "remembrance", + "harmonic", + "homework", + "##sol", + "fiancee", + "gears", + "olds", + "angelica", + "loft", + "ramsay", + "quiz", + "colliery", + "sevens", + "##cape", + "autism", + "##hil", + "walkway", + "##boats", + "ruben", + "abnormal", + "ounce", + "khmer", + "##bbe", + "zachary", + "bedside", + "morphology", + "punching", + "##olar", + "sparrow", + "convinces", + "##35", + "hewitt", + "queer", + "remastered", + "rods", + "mabel", + "solemn", + "notified", + "lyricist", + "symmetric", + "##xide", + "174", + "encore", + "passports", + "wildcats", + "##uni", + "baja", + "##pac", + "mildly", + "##ease", + "bleed", + "commodity", + "mounds", + "glossy", + "orchestras", + "##omo", + "damian", + "prelude", + "ambitions", + "##vet", + "awhile", + "remotely", + "##aud", + "asserts", + "imply", + "##iques", + "distinctly", + "modelling", + "remedy", + "##dded", + "windshield", + "dani", + "xiao", + "##endra", + "audible", + "powerplant", + "1300", + "invalid", + "elemental", + "acquisitions", + "##hala", + "immaculate", + "libby", + "plata", + "smuggling", + "ventilation", + "denoted", + "minh", + "##morphism", + "430", + "differed", + "dion", + "kelley", + "lore", + "mocking", + "sabbath", + "spikes", + "hygiene", + "drown", + "runoff", + "stylized", + "tally", + "liberated", + "aux", + "interpreter", + "righteous", + "aba", + "siren", + "reaper", + "pearce", + "millie", + "##cier", + "##yra", + "gaius", + "##iso", + "captures", + "##ttering", + "dorm", + "claudio", + "##sic", + "benches", + "knighted", + "blackness", + "##ored", + "discount", + "fumble", + "oxidation", + "routed", + "##\u03c2", + "novak", + "perpendicular", + "spoiled", + "fracture", + "splits", + "##urt", + "pads", + "topology", + "##cats", + "axes", + "fortunate", + "offenders", + "protestants", + "esteem", + "221", + "broadband", + "convened", + "frankly", + "hound", + "prototypes", + "isil", + "facilitated", + "keel", + "##sher", + "sahara", + "awaited", + "bubba", + "orb", + "prosecutors", + "186", + "hem", + "520", + "##xing", + "relaxing", + "remnant", + "romney", + "sorted", + "slalom", + "stefano", + "ulrich", + "##active", + "exemption", + "folder", + "pauses", + "foliage", + "hitchcock", + "epithet", + "204", + "criticisms", + "##aca", + "ballistic", + "brody", + "hinduism", + "chaotic", + "youths", + "equals", + "##pala", + "pts", + "thicker", + "analogous", + "capitalist", + "improvised", + "overseeing", + "sinatra", + "ascended", + "beverage", + "##tl", + "straightforward", + "##kon", + "curran", + "##west", + "bois", + "325", + "induce", + "surveying", + "emperors", + "sax", + "unpopular", + "##kk", + "cartoonist", + "fused", + "##mble", + "unto", + "##yuki", + "localities", + "##cko", + "##ln", + "darlington", + "slain", + "academie", + "lobbying", + "sediment", + "puzzles", + "##grass", + "defiance", + "dickens", + "manifest", + "tongues", + "alumnus", + "arbor", + "coincide", + "184", + "appalachian", + "mustafa", + "examiner", + "cabaret", + "traumatic", + "yves", + "bracelet", + "draining", + "heroin", + "magnum", + "baths", + "odessa", + "consonants", + "mitsubishi", + "##gua", + "kellan", + "vaudeville", + "##fr", + "joked", + "null", + "straps", + "probation", + "##\u0142aw", + "ceded", + "interfaces", + "##pas", + "##zawa", + "blinding", + "viet", + "224", + "rothschild", + "museo", + "640", + "huddersfield", + "##vr", + "tactic", + "##storm", + "brackets", + "dazed", + "incorrectly", + "##vu", + "reg", + "glazed", + "fearful", + "manifold", + "benefited", + "irony", + "##sun", + "stumbling", + "##rte", + "willingness", + "balkans", + "mei", + "wraps", + "##aba", + "injected", + "##lea", + "gu", + "syed", + "harmless", + "##hammer", + "bray", + "takeoff", + "poppy", + "timor", + "cardboard", + "astronaut", + "purdue", + "weeping", + "southbound", + "cursing", + "stalls", + "diagonal", + "##neer", + "lamar", + "bryce", + "comte", + "weekdays", + "harrington", + "##uba", + "negatively", + "##see", + "lays", + "grouping", + "##cken", + "##henko", + "affirmed", + "halle", + "modernist", + "##lai", + "hodges", + "smelling", + "aristocratic", + "baptized", + "dismiss", + "justification", + "oilers", + "##now", + "coupling", + "qin", + "snack", + "healer", + "##qing", + "gardener", + "layla", + "battled", + "formulated", + "stephenson", + "gravitational", + "##gill", + "##jun", + "1768", + "granny", + "coordinating", + "suites", + "##cd", + "##ioned", + "monarchs", + "##cote", + "##hips", + "sep", + "blended", + "apr", + "barrister", + "deposition", + "fia", + "mina", + "policemen", + "paranoid", + "##pressed", + "churchyard", + "covert", + "crumpled", + "creep", + "abandoning", + "tr", + "transmit", + "conceal", + "barr", + "understands", + "readiness", + "spire", + "##cology", + "##enia", + "##erry", + "610", + "startling", + "unlock", + "vida", + "bowled", + "slots", + "##nat", + "##islav", + "spaced", + "trusting", + "admire", + "rig", + "##ink", + "slack", + "##70", + "mv", + "207", + "casualty", + "##wei", + "classmates", + "##odes", + "##rar", + "##rked", + "amherst", + "furnished", + "evolve", + "foundry", + "menace", + "mead", + "##lein", + "flu", + "wesleyan", + "##kled", + "monterey", + "webber", + "##vos", + "wil", + "##mith", + "##\u043d\u0430", + "bartholomew", + "justices", + "restrained", + "##cke", + "amenities", + "191", + "mediated", + "sewage", + "trenches", + "ml", + "mainz", + "##thus", + "1800s", + "##cula", + "##inski", + "caine", + "bonding", + "213", + "converts", + "spheres", + "superseded", + "marianne", + "crypt", + "sweaty", + "ensign", + "historia", + "##br", + "spruce", + "##post", + "##ask", + "forks", + "thoughtfully", + "yukon", + "pamphlet", + "ames", + "##uter", + "karma", + "##yya", + "bryn", + "negotiation", + "sighs", + "incapable", + "##mbre", + "##ntial", + "actresses", + "taft", + "##mill", + "luce", + "prevailed", + "##amine", + "1773", + "motionless", + "envoy", + "testify", + "investing", + "sculpted", + "instructors", + "provence", + "kali", + "cullen", + "horseback", + "##while", + "goodwin", + "##jos", + "gaa", + "norte", + "##ldon", + "modify", + "wavelength", + "abd", + "214", + "skinned", + "sprinter", + "forecast", + "scheduling", + "marries", + "squared", + "tentative", + "##chman", + "boer", + "##isch", + "bolts", + "swap", + "fisherman", + "assyrian", + "impatiently", + "guthrie", + "martins", + "murdoch", + "194", + "tanya", + "nicely", + "dolly", + "lacy", + "med", + "##45", + "syn", + "decks", + "fashionable", + "millionaire", + "##ust", + "surfing", + "##ml", + "##ision", + "heaved", + "tammy", + "consulate", + "attendees", + "routinely", + "197", + "fuse", + "saxophonist", + "backseat", + "malaya", + "##lord", + "scowl", + "tau", + "##ishly", + "193", + "sighted", + "steaming", + "##rks", + "303", + "911", + "##holes", + "##hong", + "ching", + "##wife", + "bless", + "conserved", + "jurassic", + "stacey", + "unix", + "zion", + "chunk", + "rigorous", + "blaine", + "198", + "peabody", + "slayer", + "dismay", + "brewers", + "nz", + "##jer", + "det", + "##glia", + "glover", + "postwar", + "int", + "penetration", + "sylvester", + "imitation", + "vertically", + "airlift", + "heiress", + "knoxville", + "viva", + "##uin", + "390", + "macon", + "##rim", + "##fighter", + "##gonal", + "janice", + "##orescence", + "##wari", + "marius", + "belongings", + "leicestershire", + "196", + "blanco", + "inverted", + "preseason", + "sanity", + "sobbing", + "##due", + "##elt", + "##dled", + "collingwood", + "regeneration", + "flickering", + "shortest", + "##mount", + "##osi", + "feminism", + "##lat", + "sherlock", + "cabinets", + "fumbled", + "northbound", + "precedent", + "snaps", + "##mme", + "researching", + "##akes", + "guillaume", + "insights", + "manipulated", + "vapor", + "neighbour", + "sap", + "gangster", + "frey", + "f1", + "stalking", + "scarcely", + "callie", + "barnett", + "tendencies", + "audi", + "doomed", + "assessing", + "slung", + "panchayat", + "ambiguous", + "bartlett", + "##etto", + "distributing", + "violating", + "wolverhampton", + "##hetic", + "swami", + "histoire", + "##urus", + "liable", + "pounder", + "groin", + "hussain", + "larsen", + "popping", + "surprises", + "##atter", + "vie", + "curt", + "##station", + "mute", + "relocate", + "musicals", + "authorization", + "richter", + "##sef", + "immortality", + "tna", + "bombings", + "##press", + "deteriorated", + "yiddish", + "##acious", + "robbed", + "colchester", + "cs", + "pmid", + "ao", + "verified", + "balancing", + "apostle", + "swayed", + "recognizable", + "oxfordshire", + "retention", + "nottinghamshire", + "contender", + "judd", + "invitational", + "shrimp", + "uhf", + "##icient", + "cleaner", + "longitudinal", + "tanker", + "##mur", + "acronym", + "broker", + "koppen", + "sundance", + "suppliers", + "##gil", + "4000", + "clipped", + "fuels", + "petite", + "##anne", + "landslide", + "helene", + "diversion", + "populous", + "landowners", + "auspices", + "melville", + "quantitative", + "##xes", + "ferries", + "nicky", + "##llus", + "doo", + "haunting", + "roche", + "carver", + "downed", + "unavailable", + "##pathy", + "approximation", + "hiroshima", + "##hue", + "garfield", + "valle", + "comparatively", + "keyboardist", + "traveler", + "##eit", + "congestion", + "calculating", + "subsidiaries", + "##bate", + "serb", + "modernization", + "fairies", + "deepened", + "ville", + "averages", + "##lore", + "inflammatory", + "tonga", + "##itch", + "co\u2082", + "squads", + "##hea", + "gigantic", + "serum", + "enjoyment", + "retailer", + "verona", + "35th", + "cis", + "##phobic", + "magna", + "technicians", + "##vati", + "arithmetic", + "##sport", + "levin", + "##dation", + "amtrak", + "chow", + "sienna", + "##eyer", + "backstage", + "entrepreneurship", + "##otic", + "learnt", + "tao", + "##udy", + "worcestershire", + "formulation", + "baggage", + "hesitant", + "bali", + "sabotage", + "##kari", + "barren", + "enhancing", + "murmur", + "pl", + "freshly", + "putnam", + "syntax", + "aces", + "medicines", + "resentment", + "bandwidth", + "##sier", + "grins", + "chili", + "guido", + "##sei", + "framing", + "implying", + "gareth", + "lissa", + "genevieve", + "pertaining", + "admissions", + "geo", + "thorpe", + "proliferation", + "sato", + "bela", + "analyzing", + "parting", + "##gor", + "awakened", + "##isman", + "huddled", + "secrecy", + "##kling", + "hush", + "gentry", + "540", + "dungeons", + "##ego", + "coasts", + "##utz", + "sacrificed", + "##chule", + "landowner", + "mutually", + "prevalence", + "programmer", + "adolescent", + "disrupted", + "seaside", + "gee", + "trusts", + "vamp", + "georgie", + "##nesian", + "##iol", + "schedules", + "sindh", + "##market", + "etched", + "hm", + "sparse", + "bey", + "beaux", + "scratching", + "gliding", + "unidentified", + "216", + "collaborating", + "gems", + "jesuits", + "oro", + "accumulation", + "shaping", + "mbe", + "anal", + "##xin", + "231", + "enthusiasts", + "newscast", + "##egan", + "janata", + "dewey", + "parkinson", + "179", + "ankara", + "biennial", + "towering", + "dd", + "inconsistent", + "950", + "##chet", + "thriving", + "terminate", + "cabins", + "furiously", + "eats", + "advocating", + "donkey", + "marley", + "muster", + "phyllis", + "leiden", + "##user", + "grassland", + "glittering", + "iucn", + "loneliness", + "217", + "memorandum", + "armenians", + "##ddle", + "popularized", + "rhodesia", + "60s", + "lame", + "##illon", + "sans", + "bikini", + "header", + "orbits", + "##xx", + "##finger", + "##ulator", + "sharif", + "spines", + "biotechnology", + "strolled", + "naughty", + "yates", + "##wire", + "fremantle", + "milo", + "##mour", + "abducted", + "removes", + "##atin", + "humming", + "wonderland", + "##chrome", + "##ester", + "hume", + "pivotal", + "##rates", + "armand", + "grams", + "believers", + "elector", + "rte", + "apron", + "bis", + "scraped", + "##yria", + "endorsement", + "initials", + "##llation", + "eps", + "dotted", + "hints", + "buzzing", + "emigration", + "nearer", + "##tom", + "indicators", + "##ulu", + "coarse", + "neutron", + "protectorate", + "##uze", + "directional", + "exploits", + "pains", + "loire", + "1830s", + "proponents", + "guggenheim", + "rabbits", + "ritchie", + "305", + "hectare", + "inputs", + "hutton", + "##raz", + "verify", + "##ako", + "boilers", + "longitude", + "##lev", + "skeletal", + "yer", + "emilia", + "citrus", + "compromised", + "##gau", + "pokemon", + "prescription", + "paragraph", + "eduard", + "cadillac", + "attire", + "categorized", + "kenyan", + "weddings", + "charley", + "##bourg", + "entertain", + "monmouth", + "##lles", + "nutrients", + "davey", + "mesh", + "incentive", + "practised", + "ecosystems", + "kemp", + "subdued", + "overheard", + "##rya", + "bodily", + "maxim", + "##nius", + "apprenticeship", + "ursula", + "##fight", + "lodged", + "rug", + "silesian", + "unconstitutional", + "patel", + "inspected", + "coyote", + "unbeaten", + "##hak", + "34th", + "disruption", + "convict", + "parcel", + "##cl", + "##nham", + "collier", + "implicated", + "mallory", + "##iac", + "##lab", + "susannah", + "winkler", + "##rber", + "shia", + "phelps", + "sediments", + "graphical", + "robotic", + "##sner", + "adulthood", + "mart", + "smoked", + "##isto", + "kathryn", + "clarified", + "##aran", + "divides", + "convictions", + "oppression", + "pausing", + "burying", + "##mt", + "federico", + "mathias", + "eileen", + "##tana", + "kite", + "hunched", + "##acies", + "189", + "##atz", + "disadvantage", + "liza", + "kinetic", + "greedy", + "paradox", + "yokohama", + "dowager", + "trunks", + "ventured", + "##gement", + "gupta", + "vilnius", + "olaf", + "##thest", + "crimean", + "hopper", + "##ej", + "progressively", + "arturo", + "mouthed", + "arrondissement", + "##fusion", + "rubin", + "simulcast", + "oceania", + "##orum", + "##stra", + "##rred", + "busiest", + "intensely", + "navigator", + "cary", + "##vine", + "##hini", + "##bies", + "fife", + "rowe", + "rowland", + "posing", + "insurgents", + "shafts", + "lawsuits", + "activate", + "conor", + "inward", + "culturally", + "garlic", + "265", + "##eering", + "eclectic", + "##hui", + "##kee", + "##nl", + "furrowed", + "vargas", + "meteorological", + "rendezvous", + "##aus", + "culinary", + "commencement", + "##dition", + "quota", + "##notes", + "mommy", + "salaries", + "overlapping", + "mule", + "##iology", + "##mology", + "sums", + "wentworth", + "##isk", + "##zione", + "mainline", + "subgroup", + "##illy", + "hack", + "plaintiff", + "verdi", + "bulb", + "differentiation", + "engagements", + "multinational", + "supplemented", + "bertrand", + "caller", + "regis", + "##naire", + "##sler", + "##arts", + "##imated", + "blossom", + "propagation", + "kilometer", + "viaduct", + "vineyards", + "##uate", + "beckett", + "optimization", + "golfer", + "songwriters", + "seminal", + "semitic", + "thud", + "volatile", + "evolving", + "ridley", + "##wley", + "trivial", + "distributions", + "scandinavia", + "jiang", + "##ject", + "wrestled", + "insistence", + "##dio", + "emphasizes", + "napkin", + "##ods", + "adjunct", + "rhyme", + "##ricted", + "##eti", + "hopeless", + "surrounds", + "tremble", + "32nd", + "smoky", + "##ntly", + "oils", + "medicinal", + "padded", + "steer", + "wilkes", + "219", + "255", + "concessions", + "hue", + "uniquely", + "blinded", + "landon", + "yahoo", + "##lane", + "hendrix", + "commemorating", + "dex", + "specify", + "chicks", + "##ggio", + "intercity", + "1400", + "morley", + "##torm", + "highlighting", + "##oting", + "pang", + "oblique", + "stalled", + "##liner", + "flirting", + "newborn", + "1769", + "bishopric", + "shaved", + "232", + "currie", + "##ush", + "dharma", + "spartan", + "##ooped", + "favorites", + "smug", + "novella", + "sirens", + "abusive", + "creations", + "espana", + "##lage", + "paradigm", + "semiconductor", + "sheen", + "##rdo", + "##yen", + "##zak", + "nrl", + "renew", + "##pose", + "##tur", + "adjutant", + "marches", + "norma", + "##enity", + "ineffective", + "weimar", + "grunt", + "##gat", + "lordship", + "plotting", + "expenditure", + "infringement", + "lbs", + "refrain", + "av", + "mimi", + "mistakenly", + "postmaster", + "1771", + "##bara", + "ras", + "motorsports", + "tito", + "199", + "subjective", + "##zza", + "bully", + "stew", + "##kaya", + "prescott", + "1a", + "##raphic", + "##zam", + "bids", + "styling", + "paranormal", + "reeve", + "sneaking", + "exploding", + "katz", + "akbar", + "migrant", + "syllables", + "indefinitely", + "##ogical", + "destroys", + "replaces", + "applause", + "##phine", + "pest", + "##fide", + "218", + "articulated", + "bertie", + "##thing", + "##cars", + "##ptic", + "courtroom", + "crowley", + "aesthetics", + "cummings", + "tehsil", + "hormones", + "titanic", + "dangerously", + "##ibe", + "stadion", + "jaenelle", + "auguste", + "ciudad", + "##chu", + "mysore", + "partisans", + "##sio", + "lucan", + "philipp", + "##aly", + "debating", + "henley", + "interiors", + "##rano", + "##tious", + "homecoming", + "beyonce", + "usher", + "henrietta", + "prepares", + "weeds", + "##oman", + "ely", + "plucked", + "##pire", + "##dable", + "luxurious", + "##aq", + "artifact", + "password", + "pasture", + "juno", + "maddy", + "minsk", + "##dder", + "##ologies", + "##rone", + "assessments", + "martian", + "royalist", + "1765", + "examines", + "##mani", + "##rge", + "nino", + "223", + "parry", + "scooped", + "relativity", + "##eli", + "##uting", + "##cao", + "congregational", + "noisy", + "traverse", + "##agawa", + "strikeouts", + "nickelodeon", + "obituary", + "transylvania", + "binds", + "depictions", + "polk", + "trolley", + "##yed", + "##lard", + "breeders", + "##under", + "dryly", + "hokkaido", + "1762", + "strengths", + "stacks", + "bonaparte", + "connectivity", + "neared", + "prostitutes", + "stamped", + "anaheim", + "gutierrez", + "sinai", + "##zzling", + "bram", + "fresno", + "madhya", + "##86", + "proton", + "##lena", + "##llum", + "##phon", + "reelected", + "wanda", + "##anus", + "##lb", + "ample", + "distinguishing", + "##yler", + "grasping", + "sermons", + "tomato", + "bland", + "stimulation", + "avenues", + "##eux", + "spreads", + "scarlett", + "fern", + "pentagon", + "assert", + "baird", + "chesapeake", + "ir", + "calmed", + "distortion", + "fatalities", + "##olis", + "correctional", + "pricing", + "##astic", + "##gina", + "prom", + "dammit", + "ying", + "collaborate", + "##chia", + "welterweight", + "33rd", + "pointer", + "substitution", + "bonded", + "umpire", + "communicating", + "multitude", + "paddle", + "##obe", + "federally", + "intimacy", + "##insky", + "betray", + "ssr", + "##lett", + "##lean", + "##lves", + "##therapy", + "airbus", + "##tery", + "functioned", + "ud", + "bearer", + "biomedical", + "netflix", + "##hire", + "##nca", + "condom", + "brink", + "ik", + "##nical", + "macy", + "##bet", + "flap", + "gma", + "experimented", + "jelly", + "lavender", + "##icles", + "##ulia", + "munro", + "##mian", + "##tial", + "rye", + "##rle", + "60th", + "gigs", + "hottest", + "rotated", + "predictions", + "fuji", + "bu", + "##erence", + "##omi", + "barangay", + "##fulness", + "##sas", + "clocks", + "##rwood", + "##liness", + "cereal", + "roe", + "wight", + "decker", + "uttered", + "babu", + "onion", + "xml", + "forcibly", + "##df", + "petra", + "sarcasm", + "hartley", + "peeled", + "storytelling", + "##42", + "##xley", + "##ysis", + "##ffa", + "fibre", + "kiel", + "auditor", + "fig", + "harald", + "greenville", + "##berries", + "geographically", + "nell", + "quartz", + "##athic", + "cemeteries", + "##lr", + "crossings", + "nah", + "holloway", + "reptiles", + "chun", + "sichuan", + "snowy", + "660", + "corrections", + "##ivo", + "zheng", + "ambassadors", + "blacksmith", + "fielded", + "fluids", + "hardcover", + "turnover", + "medications", + "melvin", + "academies", + "##erton", + "ro", + "roach", + "absorbing", + "spaniards", + "colton", + "##founded", + "outsider", + "espionage", + "kelsey", + "245", + "edible", + "##ulf", + "dora", + "establishes", + "##sham", + "##tries", + "contracting", + "##tania", + "cinematic", + "costello", + "nesting", + "##uron", + "connolly", + "duff", + "##nology", + "mma", + "##mata", + "fergus", + "sexes", + "gi", + "optics", + "spectator", + "woodstock", + "banning", + "##hee", + "##fle", + "differentiate", + "outfielder", + "refinery", + "226", + "312", + "gerhard", + "horde", + "lair", + "drastically", + "##udi", + "landfall", + "##cheng", + "motorsport", + "odi", + "##achi", + "predominant", + "quay", + "skins", + "##ental", + "edna", + "harshly", + "complementary", + "murdering", + "##aves", + "wreckage", + "##90", + "ono", + "outstretched", + "lennox", + "munitions", + "galen", + "reconcile", + "470", + "scalp", + "bicycles", + "gillespie", + "questionable", + "rosenberg", + "guillermo", + "hostel", + "jarvis", + "kabul", + "volvo", + "opium", + "yd", + "##twined", + "abuses", + "decca", + "outpost", + "##cino", + "sensible", + "neutrality", + "##64", + "ponce", + "anchorage", + "atkins", + "turrets", + "inadvertently", + "disagree", + "libre", + "vodka", + "reassuring", + "weighs", + "##yal", + "glide", + "jumper", + "ceilings", + "repertory", + "outs", + "stain", + "##bial", + "envy", + "##ucible", + "smashing", + "heightened", + "policing", + "hyun", + "mixes", + "lai", + "prima", + "##ples", + "celeste", + "##bina", + "lucrative", + "intervened", + "kc", + "manually", + "##rned", + "stature", + "staffed", + "bun", + "bastards", + "nairobi", + "priced", + "##auer", + "thatcher", + "##kia", + "tripped", + "comune", + "##ogan", + "##pled", + "brasil", + "incentives", + "emanuel", + "hereford", + "musica", + "##kim", + "benedictine", + "biennale", + "##lani", + "eureka", + "gardiner", + "rb", + "knocks", + "sha", + "##ael", + "##elled", + "##onate", + "efficacy", + "ventura", + "masonic", + "sanford", + "maize", + "leverage", + "##feit", + "capacities", + "santana", + "##aur", + "novelty", + "vanilla", + "##cter", + "##tour", + "benin", + "##oir", + "##rain", + "neptune", + "drafting", + "tallinn", + "##cable", + "humiliation", + "##boarding", + "schleswig", + "fabian", + "bernardo", + "liturgy", + "spectacle", + "sweeney", + "pont", + "routledge", + "##tment", + "cosmos", + "ut", + "hilt", + "sleek", + "universally", + "##eville", + "##gawa", + "typed", + "##dry", + "favors", + "allegheny", + "glaciers", + "##rly", + "recalling", + "aziz", + "##log", + "parasite", + "requiem", + "auf", + "##berto", + "##llin", + "illumination", + "##breaker", + "##issa", + "festivities", + "bows", + "govern", + "vibe", + "vp", + "333", + "sprawled", + "larson", + "pilgrim", + "bwf", + "leaping", + "##rts", + "##ssel", + "alexei", + "greyhound", + "hoarse", + "##dler", + "##oration", + "seneca", + "##cule", + "gaping", + "##ulously", + "##pura", + "cinnamon", + "##gens", + "##rricular", + "craven", + "fantasies", + "houghton", + "engined", + "reigned", + "dictator", + "supervising", + "##oris", + "bogota", + "commentaries", + "unnatural", + "fingernails", + "spirituality", + "tighten", + "##tm", + "canadiens", + "protesting", + "intentional", + "cheers", + "sparta", + "##ytic", + "##iere", + "##zine", + "widen", + "belgarath", + "controllers", + "dodd", + "iaaf", + "navarre", + "##ication", + "defect", + "squire", + "steiner", + "whisky", + "##mins", + "560", + "inevitably", + "tome", + "##gold", + "chew", + "##uid", + "##lid", + "elastic", + "##aby", + "streaked", + "alliances", + "jailed", + "regal", + "##ined", + "##phy", + "czechoslovak", + "narration", + "absently", + "##uld", + "bluegrass", + "guangdong", + "quran", + "criticizing", + "hose", + "hari", + "##liest", + "##owa", + "skier", + "streaks", + "deploy", + "##lom", + "raft", + "bose", + "dialed", + "huff", + "##eira", + "haifa", + "simplest", + "bursting", + "endings", + "ib", + "sultanate", + "##titled", + "franks", + "whitman", + "ensures", + "sven", + "##ggs", + "collaborators", + "forster", + "organising", + "ui", + "banished", + "napier", + "injustice", + "teller", + "layered", + "thump", + "##otti", + "roc", + "battleships", + "evidenced", + "fugitive", + "sadie", + "robotics", + "##roud", + "equatorial", + "geologist", + "##iza", + "yielding", + "##bron", + "##sr", + "internationale", + "mecca", + "##diment", + "sbs", + "skyline", + "toad", + "uploaded", + "reflective", + "undrafted", + "lal", + "leafs", + "bayern", + "##dai", + "lakshmi", + "shortlisted", + "##stick", + "##wicz", + "camouflage", + "donate", + "af", + "christi", + "lau", + "##acio", + "disclosed", + "nemesis", + "1761", + "assemble", + "straining", + "northamptonshire", + "tal", + "##asi", + "bernardino", + "premature", + "heidi", + "42nd", + "coefficients", + "galactic", + "reproduce", + "buzzed", + "sensations", + "zionist", + "monsieur", + "myrtle", + "##eme", + "archery", + "strangled", + "musically", + "viewpoint", + "antiquities", + "bei", + "trailers", + "seahawks", + "cured", + "pee", + "preferring", + "tasmanian", + "lange", + "sul", + "##mail", + "##working", + "colder", + "overland", + "lucivar", + "massey", + "gatherings", + "haitian", + "##smith", + "disapproval", + "flaws", + "##cco", + "##enbach", + "1766", + "npr", + "##icular", + "boroughs", + "creole", + "forums", + "techno", + "1755", + "dent", + "abdominal", + "streetcar", + "##eson", + "##stream", + "procurement", + "gemini", + "predictable", + "##tya", + "acheron", + "christoph", + "feeder", + "fronts", + "vendor", + "bernhard", + "jammu", + "tumors", + "slang", + "##uber", + "goaltender", + "twists", + "curving", + "manson", + "vuelta", + "mer", + "peanut", + "confessions", + "pouch", + "unpredictable", + "allowance", + "theodor", + "vascular", + "##factory", + "bala", + "authenticity", + "metabolic", + "coughing", + "nanjing", + "##cea", + "pembroke", + "##bard", + "splendid", + "36th", + "ff", + "hourly", + "##ahu", + "elmer", + "handel", + "##ivate", + "awarding", + "thrusting", + "dl", + "experimentation", + "##hesion", + "##46", + "caressed", + "entertained", + "steak", + "##rangle", + "biologist", + "orphans", + "baroness", + "oyster", + "stepfather", + "##dridge", + "mirage", + "reefs", + "speeding", + "##31", + "barons", + "1764", + "227", + "inhabit", + "preached", + "repealed", + "##tral", + "honoring", + "boogie", + "captives", + "administer", + "johanna", + "##imate", + "gel", + "suspiciously", + "1767", + "sobs", + "##dington", + "backbone", + "hayward", + "garry", + "##folding", + "##nesia", + "maxi", + "##oof", + "##ppe", + "ellison", + "galileo", + "##stand", + "crimea", + "frenzy", + "amour", + "bumper", + "matrices", + "natalia", + "baking", + "garth", + "palestinians", + "##grove", + "smack", + "conveyed", + "ensembles", + "gardening", + "##manship", + "##rup", + "##stituting", + "1640", + "harvesting", + "topography", + "jing", + "shifters", + "dormitory", + "##carriage", + "##lston", + "ist", + "skulls", + "##stadt", + "dolores", + "jewellery", + "sarawak", + "##wai", + "##zier", + "fences", + "christy", + "confinement", + "tumbling", + "credibility", + "fir", + "stench", + "##bria", + "##plication", + "##nged", + "##sam", + "virtues", + "##belt", + "marjorie", + "pba", + "##eem", + "##made", + "celebrates", + "schooner", + "agitated", + "barley", + "fulfilling", + "anthropologist", + "##pro", + "restrict", + "novi", + "regulating", + "##nent", + "padres", + "##rani", + "##hesive", + "loyola", + "tabitha", + "milky", + "olson", + "proprietor", + "crambidae", + "guarantees", + "intercollegiate", + "ljubljana", + "hilda", + "##sko", + "ignorant", + "hooded", + "##lts", + "sardinia", + "##lidae", + "##vation", + "frontman", + "privileged", + "witchcraft", + "##gp", + "jammed", + "laude", + "poking", + "##than", + "bracket", + "amazement", + "yunnan", + "##erus", + "maharaja", + "linnaeus", + "264", + "commissioning", + "milano", + "peacefully", + "##logies", + "akira", + "rani", + "regulator", + "##36", + "grasses", + "##rance", + "luzon", + "crows", + "compiler", + "gretchen", + "seaman", + "edouard", + "tab", + "buccaneers", + "ellington", + "hamlets", + "whig", + "socialists", + "##anto", + "directorial", + "easton", + "mythological", + "##kr", + "##vary", + "rhineland", + "semantic", + "taut", + "dune", + "inventions", + "succeeds", + "##iter", + "replication", + "branched", + "##pired", + "jul", + "prosecuted", + "kangaroo", + "penetrated", + "##avian", + "middlesbrough", + "doses", + "bleak", + "madam", + "predatory", + "relentless", + "##vili", + "reluctance", + "##vir", + "hailey", + "crore", + "silvery", + "1759", + "monstrous", + "swimmers", + "transmissions", + "hawthorn", + "informing", + "##eral", + "toilets", + "caracas", + "crouch", + "kb", + "##sett", + "295", + "cartel", + "hadley", + "##aling", + "alexia", + "yvonne", + "##biology", + "cinderella", + "eton", + "superb", + "blizzard", + "stabbing", + "industrialist", + "maximus", + "##gm", + "##orus", + "groves", + "maud", + "clade", + "oversized", + "comedic", + "##bella", + "rosen", + "nomadic", + "fulham", + "montane", + "beverages", + "galaxies", + "redundant", + "swarm", + "##rot", + "##folia", + "##llis", + "buckinghamshire", + "fen", + "bearings", + "bahadur", + "##rom", + "gilles", + "phased", + "dynamite", + "faber", + "benoit", + "vip", + "##ount", + "##wd", + "booking", + "fractured", + "tailored", + "anya", + "spices", + "westwood", + "cairns", + "auditions", + "inflammation", + "steamed", + "##rocity", + "##acion", + "##urne", + "skyla", + "thereof", + "watford", + "torment", + "archdeacon", + "transforms", + "lulu", + "demeanor", + "fucked", + "serge", + "##sor", + "mckenna", + "minas", + "entertainer", + "##icide", + "caress", + "originate", + "residue", + "##sty", + "1740", + "##ilised", + "##org", + "beech", + "##wana", + "subsidies", + "##ghton", + "emptied", + "gladstone", + "ru", + "firefighters", + "voodoo", + "##rcle", + "het", + "nightingale", + "tamara", + "edmond", + "ingredient", + "weaknesses", + "silhouette", + "285", + "compatibility", + "withdrawing", + "hampson", + "##mona", + "anguish", + "giggling", + "##mber", + "bookstore", + "##jiang", + "southernmost", + "tilting", + "##vance", + "bai", + "economical", + "rf", + "briefcase", + "dreadful", + "hinted", + "projections", + "shattering", + "totaling", + "##rogate", + "analogue", + "indicted", + "periodical", + "fullback", + "##dman", + "haynes", + "##tenberg", + "##ffs", + "##ishment", + "1745", + "thirst", + "stumble", + "penang", + "vigorous", + "##ddling", + "##kor", + "##lium", + "octave", + "##ove", + "##enstein", + "##inen", + "##ones", + "siberian", + "##uti", + "cbn", + "repeal", + "swaying", + "##vington", + "khalid", + "tanaka", + "unicorn", + "otago", + "plastered", + "lobe", + "riddle", + "##rella", + "perch", + "##ishing", + "croydon", + "filtered", + "graeme", + "tripoli", + "##ossa", + "crocodile", + "##chers", + "sufi", + "mined", + "##tung", + "inferno", + "lsu", + "##phi", + "swelled", + "utilizes", + "\u00a32", + "cale", + "periodicals", + "styx", + "hike", + "informally", + "coop", + "lund", + "##tidae", + "ala", + "hen", + "qui", + "transformations", + "disposed", + "sheath", + "chickens", + "##cade", + "fitzroy", + "sas", + "silesia", + "unacceptable", + "odisha", + "1650", + "sabrina", + "pe", + "spokane", + "ratios", + "athena", + "massage", + "shen", + "dilemma", + "##drum", + "##riz", + "##hul", + "corona", + "doubtful", + "niall", + "##pha", + "##bino", + "fines", + "cite", + "acknowledging", + "bangor", + "ballard", + "bathurst", + "##resh", + "huron", + "mustered", + "alzheimer", + "garments", + "kinase", + "tyre", + "warship", + "##cp", + "flashback", + "pulmonary", + "braun", + "cheat", + "kamal", + "cyclists", + "constructions", + "grenades", + "ndp", + "traveller", + "excuses", + "stomped", + "signalling", + "trimmed", + "futsal", + "mosques", + "relevance", + "##wine", + "wta", + "##23", + "##vah", + "##lter", + "hoc", + "##riding", + "optimistic", + "##\u00b4s", + "deco", + "sim", + "interacting", + "rejecting", + "moniker", + "waterways", + "##ieri", + "##oku", + "mayors", + "gdansk", + "outnumbered", + "pearls", + "##ended", + "##hampton", + "fairs", + "totals", + "dominating", + "262", + "notions", + "stairway", + "compiling", + "pursed", + "commodities", + "grease", + "yeast", + "##jong", + "carthage", + "griffiths", + "residual", + "amc", + "contraction", + "laird", + "sapphire", + "##marine", + "##ivated", + "amalgamation", + "dissolve", + "inclination", + "lyle", + "packaged", + "altitudes", + "suez", + "canons", + "graded", + "lurched", + "narrowing", + "boasts", + "guise", + "wed", + "enrico", + "##ovsky", + "rower", + "scarred", + "bree", + "cub", + "iberian", + "protagonists", + "bargaining", + "proposing", + "trainers", + "voyages", + "vans", + "fishes", + "##aea", + "##ivist", + "##verance", + "encryption", + "artworks", + "kazan", + "sabre", + "cleopatra", + "hepburn", + "rotting", + "supremacy", + "mecklenburg", + "##brate", + "burrows", + "hazards", + "outgoing", + "flair", + "organizes", + "##ctions", + "scorpion", + "##usions", + "boo", + "234", + "chevalier", + "dunedin", + "slapping", + "##34", + "ineligible", + "pensions", + "##38", + "##omic", + "manufactures", + "emails", + "bismarck", + "238", + "weakening", + "blackish", + "ding", + "mcgee", + "quo", + "##rling", + "northernmost", + "xx", + "manpower", + "greed", + "sampson", + "clicking", + "##ange", + "##horpe", + "##inations", + "##roving", + "torre", + "##eptive", + "##moral", + "symbolism", + "38th", + "asshole", + "meritorious", + "outfits", + "splashed", + "biographies", + "sprung", + "astros", + "##tale", + "302", + "737", + "filly", + "raoul", + "nw", + "tokugawa", + "linden", + "clubhouse", + "##apa", + "tracts", + "romano", + "##pio", + "putin", + "tags", + "##note", + "chained", + "dickson", + "gunshot", + "moe", + "gunn", + "rashid", + "##tails", + "zipper", + "##bas", + "##nea", + "contrasted", + "##ply", + "##udes", + "plum", + "pharaoh", + "##pile", + "aw", + "comedies", + "ingrid", + "sandwiches", + "subdivisions", + "1100", + "mariana", + "nokia", + "kamen", + "hz", + "delaney", + "veto", + "herring", + "##words", + "possessive", + "outlines", + "##roup", + "siemens", + "stairwell", + "rc", + "gallantry", + "messiah", + "palais", + "yells", + "233", + "zeppelin", + "##dm", + "bolivar", + "##cede", + "smackdown", + "mckinley", + "##mora", + "##yt", + "muted", + "geologic", + "finely", + "unitary", + "avatar", + "hamas", + "maynard", + "rees", + "bog", + "contrasting", + "##rut", + "liv", + "chico", + "disposition", + "pixel", + "##erate", + "becca", + "dmitry", + "yeshiva", + "narratives", + "##lva", + "##ulton", + "mercenary", + "sharpe", + "tempered", + "navigate", + "stealth", + "amassed", + "keynes", + "##lini", + "untouched", + "##rrie", + "havoc", + "lithium", + "##fighting", + "abyss", + "graf", + "southward", + "wolverine", + "balloons", + "implements", + "ngos", + "transitions", + "##icum", + "ambushed", + "concacaf", + "dormant", + "economists", + "##dim", + "costing", + "csi", + "rana", + "universite", + "boulders", + "verity", + "##llon", + "collin", + "mellon", + "misses", + "cypress", + "fluorescent", + "lifeless", + "spence", + "##ulla", + "crewe", + "shepard", + "pak", + "revelations", + "##\u0645", + "jolly", + "gibbons", + "paw", + "##dro", + "##quel", + "freeing", + "##test", + "shack", + "fries", + "palatine", + "##51", + "##hiko", + "accompaniment", + "cruising", + "recycled", + "##aver", + "erwin", + "sorting", + "synthesizers", + "dyke", + "realities", + "sg", + "strides", + "enslaved", + "wetland", + "##ghan", + "competence", + "gunpowder", + "grassy", + "maroon", + "reactors", + "objection", + "##oms", + "carlson", + "gearbox", + "macintosh", + "radios", + "shelton", + "##sho", + "clergyman", + "prakash", + "254", + "mongols", + "trophies", + "oricon", + "228", + "stimuli", + "twenty20", + "cantonese", + "cortes", + "mirrored", + "##saurus", + "bhp", + "cristina", + "melancholy", + "##lating", + "enjoyable", + "nuevo", + "##wny", + "downfall", + "schumacher", + "##ind", + "banging", + "lausanne", + "rumbled", + "paramilitary", + "reflex", + "ax", + "amplitude", + "migratory", + "##gall", + "##ups", + "midi", + "barnard", + "lastly", + "sherry", + "##hp", + "##nall", + "keystone", + "##kra", + "carleton", + "slippery", + "##53", + "coloring", + "foe", + "socket", + "otter", + "##rgos", + "mats", + "##tose", + "consultants", + "bafta", + "bison", + "topping", + "##km", + "490", + "primal", + "abandonment", + "transplant", + "atoll", + "hideous", + "mort", + "pained", + "reproduced", + "tae", + "howling", + "##turn", + "unlawful", + "billionaire", + "hotter", + "poised", + "lansing", + "##chang", + "dinamo", + "retro", + "messing", + "nfc", + "domesday", + "##mina", + "blitz", + "timed", + "##athing", + "##kley", + "ascending", + "gesturing", + "##izations", + "signaled", + "tis", + "chinatown", + "mermaid", + "savanna", + "jameson", + "##aint", + "catalina", + "##pet", + "##hers", + "cochrane", + "cy", + "chatting", + "##kus", + "alerted", + "computation", + "mused", + "noelle", + "majestic", + "mohawk", + "campo", + "octagonal", + "##sant", + "##hend", + "241", + "aspiring", + "##mart", + "comprehend", + "iona", + "paralyzed", + "shimmering", + "swindon", + "rhone", + "##eley", + "reputed", + "configurations", + "pitchfork", + "agitation", + "francais", + "gillian", + "lipstick", + "##ilo", + "outsiders", + "pontifical", + "resisting", + "bitterness", + "sewer", + "rockies", + "##edd", + "##ucher", + "misleading", + "1756", + "exiting", + "galloway", + "##nging", + "risked", + "##heart", + "246", + "commemoration", + "schultz", + "##rka", + "integrating", + "##rsa", + "poses", + "shrieked", + "##weiler", + "guineas", + "gladys", + "jerking", + "owls", + "goldsmith", + "nightly", + "penetrating", + "##unced", + "lia", + "##33", + "ignited", + "betsy", + "##aring", + "##thorpe", + "follower", + "vigorously", + "##rave", + "coded", + "kiran", + "knit", + "zoology", + "tbilisi", + "##28", + "##bered", + "repository", + "govt", + "deciduous", + "dino", + "growling", + "##bba", + "enhancement", + "unleashed", + "chanting", + "pussy", + "biochemistry", + "##eric", + "kettle", + "repression", + "toxicity", + "nrhp", + "##arth", + "##kko", + "##bush", + "ernesto", + "commended", + "outspoken", + "242", + "mca", + "parchment", + "sms", + "kristen", + "##aton", + "bisexual", + "raked", + "glamour", + "navajo", + "a2", + "conditioned", + "showcased", + "##hma", + "spacious", + "youthful", + "##esa", + "usl", + "appliances", + "junta", + "brest", + "layne", + "conglomerate", + "enchanted", + "chao", + "loosened", + "picasso", + "circulating", + "inspect", + "montevideo", + "##centric", + "##kti", + "piazza", + "spurred", + "##aith", + "bari", + "freedoms", + "poultry", + "stamford", + "lieu", + "##ect", + "indigo", + "sarcastic", + "bahia", + "stump", + "attach", + "dvds", + "frankenstein", + "lille", + "approx", + "scriptures", + "pollen", + "##script", + "nmi", + "overseen", + "##ivism", + "tides", + "proponent", + "newmarket", + "inherit", + "milling", + "##erland", + "centralized", + "##rou", + "distributors", + "credentials", + "drawers", + "abbreviation", + "##lco", + "##xon", + "downing", + "uncomfortably", + "ripe", + "##oes", + "erase", + "franchises", + "##ever", + "populace", + "##bery", + "##khar", + "decomposition", + "pleas", + "##tet", + "daryl", + "sabah", + "##stle", + "##wide", + "fearless", + "genie", + "lesions", + "annette", + "##ogist", + "oboe", + "appendix", + "nair", + "dripped", + "petitioned", + "maclean", + "mosquito", + "parrot", + "rpg", + "hampered", + "1648", + "operatic", + "reservoirs", + "##tham", + "irrelevant", + "jolt", + "summarized", + "##fp", + "medallion", + "##taff", + "##\u2212", + "clawed", + "harlow", + "narrower", + "goddard", + "marcia", + "bodied", + "fremont", + "suarez", + "altering", + "tempest", + "mussolini", + "porn", + "##isms", + "sweetly", + "oversees", + "walkers", + "solitude", + "grimly", + "shrines", + "hk", + "ich", + "supervisors", + "hostess", + "dietrich", + "legitimacy", + "brushes", + "expressive", + "##yp", + "dissipated", + "##rse", + "localized", + "systemic", + "##nikov", + "gettysburg", + "##js", + "##uaries", + "dialogues", + "muttering", + "251", + "housekeeper", + "sicilian", + "discouraged", + "##frey", + "beamed", + "kaladin", + "halftime", + "kidnap", + "##amo", + "##llet", + "1754", + "synonymous", + "depleted", + "instituto", + "insulin", + "reprised", + "##opsis", + "clashed", + "##ctric", + "interrupting", + "radcliffe", + "insisting", + "medici", + "1715", + "ejected", + "playfully", + "turbulent", + "##47", + "starvation", + "##rini", + "shipment", + "rebellious", + "petersen", + "verification", + "merits", + "##rified", + "cakes", + "##charged", + "1757", + "milford", + "shortages", + "spying", + "fidelity", + "##aker", + "emitted", + "storylines", + "harvested", + "seismic", + "##iform", + "cheung", + "kilda", + "theoretically", + "barbie", + "lynx", + "##rgy", + "##tius", + "goblin", + "mata", + "poisonous", + "##nburg", + "reactive", + "residues", + "obedience", + "##\u0435\u0432\u0438\u0447", + "conjecture", + "##rac", + "401", + "hating", + "sixties", + "kicker", + "moaning", + "motown", + "##bha", + "emancipation", + "neoclassical", + "##hering", + "consoles", + "ebert", + "professorship", + "##tures", + "sustaining", + "assaults", + "obeyed", + "affluent", + "incurred", + "tornadoes", + "##eber", + "##zow", + "emphasizing", + "highlanders", + "cheated", + "helmets", + "##ctus", + "internship", + "terence", + "bony", + "executions", + "legislators", + "berries", + "peninsular", + "tinged", + "##aco", + "1689", + "amplifier", + "corvette", + "ribbons", + "lavish", + "pennant", + "##lander", + "worthless", + "##chfield", + "##forms", + "mariano", + "pyrenees", + "expenditures", + "##icides", + "chesterfield", + "mandir", + "tailor", + "39th", + "sergey", + "nestled", + "willed", + "aristocracy", + "devotees", + "goodnight", + "raaf", + "rumored", + "weaponry", + "remy", + "appropriations", + "harcourt", + "burr", + "riaa", + "##lence", + "limitation", + "unnoticed", + "guo", + "soaking", + "swamps", + "##tica", + "collapsing", + "tatiana", + "descriptive", + "brigham", + "psalm", + "##chment", + "maddox", + "##lization", + "patti", + "caliph", + "##aja", + "akron", + "injuring", + "serra", + "##ganj", + "basins", + "##sari", + "astonished", + "launcher", + "##church", + "hilary", + "wilkins", + "sewing", + "##sf", + "stinging", + "##fia", + "##ncia", + "underwood", + "startup", + "##ition", + "compilations", + "vibrations", + "embankment", + "jurist", + "##nity", + "bard", + "juventus", + "groundwater", + "kern", + "palaces", + "helium", + "boca", + "cramped", + "marissa", + "soto", + "##worm", + "jae", + "princely", + "##ggy", + "faso", + "bazaar", + "warmly", + "##voking", + "229", + "pairing", + "##lite", + "##grate", + "##nets", + "wien", + "freaked", + "ulysses", + "rebirth", + "##alia", + "##rent", + "mummy", + "guzman", + "jimenez", + "stilled", + "##nitz", + "trajectory", + "tha", + "woken", + "archival", + "professions", + "##pts", + "##pta", + "hilly", + "shadowy", + "shrink", + "##bolt", + "norwood", + "glued", + "migrate", + "stereotypes", + "devoid", + "##pheus", + "625", + "evacuate", + "horrors", + "infancy", + "gotham", + "knowles", + "optic", + "downloaded", + "sachs", + "kingsley", + "parramatta", + "darryl", + "mor", + "##onale", + "shady", + "commence", + "confesses", + "kan", + "##meter", + "##placed", + "marlborough", + "roundabout", + "regents", + "frigates", + "io", + "##imating", + "gothenburg", + "revoked", + "carvings", + "clockwise", + "convertible", + "intruder", + "##sche", + "banged", + "##ogo", + "vicky", + "bourgeois", + "##mony", + "dupont", + "footing", + "##gum", + "pd", + "##real", + "buckle", + "yun", + "penthouse", + "sane", + "720", + "serviced", + "stakeholders", + "neumann", + "bb", + "##eers", + "comb", + "##gam", + "catchment", + "pinning", + "rallies", + "typing", + "##elles", + "forefront", + "freiburg", + "sweetie", + "giacomo", + "widowed", + "goodwill", + "worshipped", + "aspirations", + "midday", + "##vat", + "fishery", + "##trick", + "bournemouth", + "turk", + "243", + "hearth", + "ethanol", + "guadalajara", + "murmurs", + "sl", + "##uge", + "afforded", + "scripted", + "##hta", + "wah", + "##jn", + "coroner", + "translucent", + "252", + "memorials", + "puck", + "progresses", + "clumsy", + "##race", + "315", + "candace", + "recounted", + "##27", + "##slin", + "##uve", + "filtering", + "##mac", + "howl", + "strata", + "heron", + "leveled", + "##ays", + "dubious", + "##oja", + "##\u0442", + "##wheel", + "citations", + "exhibiting", + "##laya", + "##mics", + "##pods", + "turkic", + "##lberg", + "injunction", + "##ennial", + "##mit", + "antibodies", + "##44", + "organise", + "##rigues", + "cardiovascular", + "cushion", + "inverness", + "##zquez", + "dia", + "cocoa", + "sibling", + "##tman", + "##roid", + "expanse", + "feasible", + "tunisian", + "algiers", + "##relli", + "rus", + "bloomberg", + "dso", + "westphalia", + "bro", + "tacoma", + "281", + "downloads", + "##ours", + "konrad", + "duran", + "##hdi", + "continuum", + "jett", + "compares", + "legislator", + "secession", + "##nable", + "##gues", + "##zuka", + "translating", + "reacher", + "##gley", + "##\u0142a", + "aleppo", + "##agi", + "tc", + "orchards", + "trapping", + "linguist", + "versatile", + "drumming", + "postage", + "calhoun", + "superiors", + "##mx", + "barefoot", + "leary", + "##cis", + "ignacio", + "alfa", + "kaplan", + "##rogen", + "bratislava", + "mori", + "##vot", + "disturb", + "haas", + "313", + "cartridges", + "gilmore", + "radiated", + "salford", + "tunic", + "hades", + "##ulsive", + "archeological", + "delilah", + "magistrates", + "auditioned", + "brewster", + "charters", + "empowerment", + "blogs", + "cappella", + "dynasties", + "iroquois", + "whipping", + "##krishna", + "raceway", + "truths", + "myra", + "weaken", + "judah", + "mcgregor", + "##horse", + "mic", + "refueling", + "37th", + "burnley", + "bosses", + "markus", + "premio", + "query", + "##gga", + "dunbar", + "##economic", + "darkest", + "lyndon", + "sealing", + "commendation", + "reappeared", + "##mun", + "addicted", + "ezio", + "slaughtered", + "satisfactory", + "shuffle", + "##eves", + "##thic", + "##uj", + "fortification", + "warrington", + "##otto", + "resurrected", + "fargo", + "mane", + "##utable", + "##lei", + "##space", + "foreword", + "ox", + "##aris", + "##vern", + "abrams", + "hua", + "##mento", + "sakura", + "##alo", + "uv", + "sentimental", + "##skaya", + "midfield", + "##eses", + "sturdy", + "scrolls", + "macleod", + "##kyu", + "entropy", + "##lance", + "mitochondrial", + "cicero", + "excelled", + "thinner", + "convoys", + "perceive", + "##oslav", + "##urable", + "systematically", + "grind", + "burkina", + "287", + "##tagram", + "ops", + "##aman", + "guantanamo", + "##cloth", + "##tite", + "forcefully", + "wavy", + "##jou", + "pointless", + "##linger", + "##tze", + "layton", + "portico", + "superficial", + "clerical", + "outlaws", + "##hism", + "burials", + "muir", + "##inn", + "creditors", + "hauling", + "rattle", + "##leg", + "calais", + "monde", + "archers", + "reclaimed", + "dwell", + "wexford", + "hellenic", + "falsely", + "remorse", + "##tek", + "dough", + "furnishings", + "##uttered", + "gabon", + "neurological", + "novice", + "##igraphy", + "contemplated", + "pulpit", + "nightstand", + "saratoga", + "##istan", + "documenting", + "pulsing", + "taluk", + "##firmed", + "busted", + "marital", + "##rien", + "disagreements", + "wasps", + "##yes", + "hodge", + "mcdonnell", + "mimic", + "fran", + "pendant", + "dhabi", + "musa", + "##nington", + "congratulations", + "argent", + "darrell", + "concussion", + "losers", + "regrets", + "thessaloniki", + "reversal", + "donaldson", + "hardwood", + "thence", + "achilles", + "ritter", + "##eran", + "demonic", + "jurgen", + "prophets", + "goethe", + "eki", + "classmate", + "buff", + "##cking", + "yank", + "irrational", + "##inging", + "perished", + "seductive", + "qur", + "sourced", + "##crat", + "##typic", + "mustard", + "ravine", + "barre", + "horizontally", + "characterization", + "phylogenetic", + "boise", + "##dit", + "##runner", + "##tower", + "brutally", + "intercourse", + "seduce", + "##bbing", + "fay", + "ferris", + "ogden", + "amar", + "nik", + "unarmed", + "##inator", + "evaluating", + "kyrgyzstan", + "sweetness", + "##lford", + "##oki", + "mccormick", + "meiji", + "notoriety", + "stimulate", + "disrupt", + "figuring", + "instructional", + "mcgrath", + "##zoo", + "groundbreaking", + "##lto", + "flinch", + "khorasan", + "agrarian", + "bengals", + "mixer", + "radiating", + "##sov", + "ingram", + "pitchers", + "nad", + "tariff", + "##cript", + "tata", + "##codes", + "##emi", + "##ungen", + "appellate", + "lehigh", + "##bled", + "##giri", + "brawl", + "duct", + "texans", + "##ciation", + "##ropolis", + "skipper", + "speculative", + "vomit", + "doctrines", + "stresses", + "253", + "davy", + "graders", + "whitehead", + "jozef", + "timely", + "cumulative", + "haryana", + "paints", + "appropriately", + "boon", + "cactus", + "##ales", + "##pid", + "dow", + "legions", + "##pit", + "perceptions", + "1730", + "picturesque", + "##yse", + "periphery", + "rune", + "wr", + "##aha", + "celtics", + "sentencing", + "whoa", + "##erin", + "confirms", + "variance", + "425", + "moines", + "mathews", + "spade", + "rave", + "m1", + "fronted", + "fx", + "blending", + "alleging", + "reared", + "##gl", + "237", + "##paper", + "grassroots", + "eroded", + "##free", + "##physical", + "directs", + "ordeal", + "##s\u0142aw", + "accelerate", + "hacker", + "rooftop", + "##inia", + "lev", + "buys", + "cebu", + "devote", + "##lce", + "specialising", + "##ulsion", + "choreographed", + "repetition", + "warehouses", + "##ryl", + "paisley", + "tuscany", + "analogy", + "sorcerer", + "hash", + "huts", + "shards", + "descends", + "exclude", + "nix", + "chaplin", + "gaga", + "ito", + "vane", + "##drich", + "causeway", + "misconduct", + "limo", + "orchestrated", + "glands", + "jana", + "##kot", + "u2", + "##mple", + "##sons", + "branching", + "contrasts", + "scoop", + "longed", + "##virus", + "chattanooga", + "##75", + "syrup", + "cornerstone", + "##tized", + "##mind", + "##iaceae", + "careless", + "precedence", + "frescoes", + "##uet", + "chilled", + "consult", + "modelled", + "snatch", + "peat", + "##thermal", + "caucasian", + "humane", + "relaxation", + "spins", + "temperance", + "##lbert", + "occupations", + "lambda", + "hybrids", + "moons", + "mp3", + "##oese", + "247", + "rolf", + "societal", + "yerevan", + "ness", + "##ssler", + "befriended", + "mechanized", + "nominate", + "trough", + "boasted", + "cues", + "seater", + "##hom", + "bends", + "##tangle", + "conductors", + "emptiness", + "##lmer", + "eurasian", + "adriatic", + "tian", + "##cie", + "anxiously", + "lark", + "propellers", + "chichester", + "jock", + "ev", + "2a", + "##holding", + "credible", + "recounts", + "tori", + "loyalist", + "abduction", + "##hoot", + "##redo", + "nepali", + "##mite", + "ventral", + "tempting", + "##ango", + "##crats", + "steered", + "##wice", + "javelin", + "dipping", + "laborers", + "prentice", + "looming", + "titanium", + "##\u02d0", + "badges", + "emir", + "tensor", + "##ntation", + "egyptians", + "rash", + "denies", + "hawthorne", + "lombard", + "showers", + "wehrmacht", + "dietary", + "trojan", + "##reus", + "welles", + "executing", + "horseshoe", + "lifeboat", + "##lak", + "elsa", + "infirmary", + "nearing", + "roberta", + "boyer", + "mutter", + "trillion", + "joanne", + "##fine", + "##oked", + "sinks", + "vortex", + "uruguayan", + "clasp", + "sirius", + "##block", + "accelerator", + "prohibit", + "sunken", + "byu", + "chronological", + "diplomats", + "ochreous", + "510", + "symmetrical", + "1644", + "maia", + "##tology", + "salts", + "reigns", + "atrocities", + "##\u0438\u044f", + "hess", + "bared", + "issn", + "##vyn", + "cater", + "saturated", + "##cycle", + "##isse", + "sable", + "voyager", + "dyer", + "yusuf", + "##inge", + "fountains", + "wolff", + "##39", + "##nni", + "engraving", + "rollins", + "atheist", + "ominous", + "##ault", + "herr", + "chariot", + "martina", + "strung", + "##fell", + "##farlane", + "horrific", + "sahib", + "gazes", + "saetan", + "erased", + "ptolemy", + "##olic", + "flushing", + "lauderdale", + "analytic", + "##ices", + "530", + "navarro", + "beak", + "gorilla", + "herrera", + "broom", + "guadalupe", + "raiding", + "sykes", + "311", + "bsc", + "deliveries", + "1720", + "invasions", + "carmichael", + "tajikistan", + "thematic", + "ecumenical", + "sentiments", + "onstage", + "##rians", + "##brand", + "##sume", + "catastrophic", + "flanks", + "molten", + "##arns", + "waller", + "aimee", + "terminating", + "##icing", + "alternately", + "##oche", + "nehru", + "printers", + "outraged", + "##eving", + "empires", + "template", + "banners", + "repetitive", + "za", + "##oise", + "vegetarian", + "##tell", + "guiana", + "opt", + "cavendish", + "lucknow", + "synthesized", + "##hani", + "##mada", + "finalized", + "##ctable", + "fictitious", + "mayoral", + "unreliable", + "##enham", + "embracing", + "peppers", + "rbis", + "##chio", + "##neo", + "inhibition", + "slashed", + "togo", + "orderly", + "embroidered", + "safari", + "salty", + "236", + "barron", + "benito", + "totaled", + "##dak", + "pubs", + "simulated", + "caden", + "devin", + "tolkien", + "momma", + "welding", + "sesame", + "##ept", + "gottingen", + "hardness", + "630", + "shaman", + "temeraire", + "620", + "adequately", + "pediatric", + "##kit", + "ck", + "assertion", + "radicals", + "composure", + "cadence", + "seafood", + "beaufort", + "lazarus", + "mani", + "warily", + "cunning", + "kurdistan", + "249", + "cantata", + "##kir", + "ares", + "##41", + "##clusive", + "nape", + "townland", + "geared", + "insulted", + "flutter", + "boating", + "violate", + "draper", + "dumping", + "malmo", + "##hh", + "##romatic", + "firearm", + "alta", + "bono", + "obscured", + "##clave", + "exceeds", + "panorama", + "unbelievable", + "##train", + "preschool", + "##essed", + "disconnected", + "installing", + "rescuing", + "secretaries", + "accessibility", + "##castle", + "##drive", + "##ifice", + "##film", + "bouts", + "slug", + "waterway", + "mindanao", + "##buro", + "##ratic", + "halves", + "##\u0644", + "calming", + "liter", + "maternity", + "adorable", + "bragg", + "electrification", + "mcc", + "##dote", + "roxy", + "schizophrenia", + "##body", + "munoz", + "kaye", + "whaling", + "239", + "mil", + "tingling", + "tolerant", + "##ago", + "unconventional", + "volcanoes", + "##finder", + "deportivo", + "##llie", + "robson", + "kaufman", + "neuroscience", + "wai", + "deportation", + "masovian", + "scraping", + "converse", + "##bh", + "hacking", + "bulge", + "##oun", + "administratively", + "yao", + "580", + "amp", + "mammoth", + "booster", + "claremont", + "hooper", + "nomenclature", + "pursuits", + "mclaughlin", + "melinda", + "##sul", + "catfish", + "barclay", + "substrates", + "taxa", + "zee", + "originals", + "kimberly", + "packets", + "padma", + "##ality", + "borrowing", + "ostensibly", + "solvent", + "##bri", + "##genesis", + "##mist", + "lukas", + "shreveport", + "veracruz", + "##\u044c", + "##lou", + "##wives", + "cheney", + "tt", + "anatolia", + "hobbs", + "##zyn", + "cyclic", + "radiant", + "alistair", + "greenish", + "siena", + "dat", + "independents", + "##bation", + "conform", + "pieter", + "hyper", + "applicant", + "bradshaw", + "spores", + "telangana", + "vinci", + "inexpensive", + "nuclei", + "322", + "jang", + "nme", + "soho", + "spd", + "##ign", + "cradled", + "receptionist", + "pow", + "##43", + "##rika", + "fascism", + "##ifer", + "experimenting", + "##ading", + "##iec", + "##region", + "345", + "jocelyn", + "maris", + "stair", + "nocturnal", + "toro", + "constabulary", + "elgin", + "##kker", + "msc", + "##giving", + "##schen", + "##rase", + "doherty", + "doping", + "sarcastically", + "batter", + "maneuvers", + "##cano", + "##apple", + "##gai", + "##git", + "intrinsic", + "##nst", + "##stor", + "1753", + "showtime", + "cafes", + "gasps", + "lviv", + "ushered", + "##thed", + "fours", + "restart", + "astonishment", + "transmitting", + "flyer", + "shrugs", + "##sau", + "intriguing", + "cones", + "dictated", + "mushrooms", + "medial", + "##kovsky", + "##elman", + "escorting", + "gaped", + "##26", + "godfather", + "##door", + "##sell", + "djs", + "recaptured", + "timetable", + "vila", + "1710", + "3a", + "aerodrome", + "mortals", + "scientology", + "##orne", + "angelina", + "mag", + "convection", + "unpaid", + "insertion", + "intermittent", + "lego", + "##nated", + "endeavor", + "kota", + "pereira", + "##lz", + "304", + "bwv", + "glamorgan", + "insults", + "agatha", + "fey", + "##cend", + "fleetwood", + "mahogany", + "protruding", + "steamship", + "zeta", + "##arty", + "mcguire", + "suspense", + "##sphere", + "advising", + "urges", + "##wala", + "hurriedly", + "meteor", + "gilded", + "inline", + "arroyo", + "stalker", + "##oge", + "excitedly", + "revered", + "##cure", + "earle", + "introductory", + "##break", + "##ilde", + "mutants", + "puff", + "pulses", + "reinforcement", + "##haling", + "curses", + "lizards", + "stalk", + "correlated", + "##fixed", + "fallout", + "macquarie", + "##unas", + "bearded", + "denton", + "heaving", + "802", + "##ocation", + "winery", + "assign", + "dortmund", + "##lkirk", + "everest", + "invariant", + "charismatic", + "susie", + "##elling", + "bled", + "lesley", + "telegram", + "sumner", + "bk", + "##ogen", + "##\u043a", + "wilcox", + "needy", + "colbert", + "duval", + "##iferous", + "##mbled", + "allotted", + "attends", + "imperative", + "##hita", + "replacements", + "hawker", + "##inda", + "insurgency", + "##zee", + "##eke", + "casts", + "##yla", + "680", + "ives", + "transitioned", + "##pack", + "##powering", + "authoritative", + "baylor", + "flex", + "cringed", + "plaintiffs", + "woodrow", + "##skie", + "drastic", + "ape", + "aroma", + "unfolded", + "commotion", + "nt", + "preoccupied", + "theta", + "routines", + "lasers", + "privatization", + "wand", + "domino", + "ek", + "clenching", + "nsa", + "strategically", + "showered", + "bile", + "handkerchief", + "pere", + "storing", + "christophe", + "insulting", + "316", + "nakamura", + "romani", + "asiatic", + "magdalena", + "palma", + "cruises", + "stripping", + "405", + "konstantin", + "soaring", + "##berman", + "colloquially", + "forerunner", + "havilland", + "incarcerated", + "parasites", + "sincerity", + "##utus", + "disks", + "plank", + "saigon", + "##ining", + "corbin", + "homo", + "ornaments", + "powerhouse", + "##tlement", + "chong", + "fastened", + "feasibility", + "idf", + "morphological", + "usable", + "##nish", + "##zuki", + "aqueduct", + "jaguars", + "keepers", + "##flies", + "aleksandr", + "faust", + "assigns", + "ewing", + "bacterium", + "hurled", + "tricky", + "hungarians", + "integers", + "wallis", + "321", + "yamaha", + "##isha", + "hushed", + "oblivion", + "aviator", + "evangelist", + "friars", + "##eller", + "monograph", + "ode", + "##nary", + "airplanes", + "labourers", + "charms", + "##nee", + "1661", + "hagen", + "tnt", + "rudder", + "fiesta", + "transcript", + "dorothea", + "ska", + "inhibitor", + "maccabi", + "retorted", + "raining", + "encompassed", + "clauses", + "menacing", + "1642", + "lineman", + "##gist", + "vamps", + "##ape", + "##dick", + "gloom", + "##rera", + "dealings", + "easing", + "seekers", + "##nut", + "##pment", + "helens", + "unmanned", + "##anu", + "##isson", + "basics", + "##amy", + "##ckman", + "adjustments", + "1688", + "brutality", + "horne", + "##zell", + "sui", + "##55", + "##mable", + "aggregator", + "##thal", + "rhino", + "##drick", + "##vira", + "counters", + "zoom", + "##01", + "##rting", + "mn", + "montenegrin", + "packard", + "##unciation", + "##\u266d", + "##kki", + "reclaim", + "scholastic", + "thugs", + "pulsed", + "##icia", + "syriac", + "quan", + "saddam", + "banda", + "kobe", + "blaming", + "buddies", + "dissent", + "##lusion", + "##usia", + "corbett", + "jaya", + "delle", + "erratic", + "lexie", + "##hesis", + "435", + "amiga", + "hermes", + "##pressing", + "##leen", + "chapels", + "gospels", + "jamal", + "##uating", + "compute", + "revolving", + "warp", + "##sso", + "##thes", + "armory", + "##eras", + "##gol", + "antrim", + "loki", + "##kow", + "##asian", + "##good", + "##zano", + "braid", + "handwriting", + "subdistrict", + "funky", + "pantheon", + "##iculate", + "concurrency", + "estimation", + "improper", + "juliana", + "##his", + "newcomers", + "johnstone", + "staten", + "communicated", + "##oco", + "##alle", + "sausage", + "stormy", + "##stered", + "##tters", + "superfamily", + "##grade", + "acidic", + "collateral", + "tabloid", + "##oped", + "##rza", + "bladder", + "austen", + "##ellant", + "mcgraw", + "##hay", + "hannibal", + "mein", + "aquino", + "lucifer", + "wo", + "badger", + "boar", + "cher", + "christensen", + "greenberg", + "interruption", + "##kken", + "jem", + "244", + "mocked", + "bottoms", + "cambridgeshire", + "##lide", + "sprawling", + "##bbly", + "eastwood", + "ghent", + "synth", + "##buck", + "advisers", + "##bah", + "nominally", + "hapoel", + "qu", + "daggers", + "estranged", + "fabricated", + "towels", + "vinnie", + "wcw", + "misunderstanding", + "anglia", + "nothin", + "unmistakable", + "##dust", + "##lova", + "chilly", + "marquette", + "truss", + "##edge", + "##erine", + "reece", + "##lty", + "##chemist", + "##connected", + "272", + "308", + "41st", + "bash", + "raion", + "waterfalls", + "##ump", + "##main", + "labyrinth", + "queue", + "theorist", + "##istle", + "bharatiya", + "flexed", + "soundtracks", + "rooney", + "leftist", + "patrolling", + "wharton", + "plainly", + "alleviate", + "eastman", + "schuster", + "topographic", + "engages", + "immensely", + "unbearable", + "fairchild", + "1620", + "dona", + "lurking", + "parisian", + "oliveira", + "ia", + "indictment", + "hahn", + "bangladeshi", + "##aster", + "vivo", + "##uming", + "##ential", + "antonia", + "expects", + "indoors", + "kildare", + "harlan", + "##logue", + "##ogenic", + "##sities", + "forgiven", + "##wat", + "childish", + "tavi", + "##mide", + "##orra", + "plausible", + "grimm", + "successively", + "scooted", + "##bola", + "##dget", + "##rith", + "spartans", + "emery", + "flatly", + "azure", + "epilogue", + "##wark", + "flourish", + "##iny", + "##tracted", + "##overs", + "##oshi", + "bestseller", + "distressed", + "receipt", + "spitting", + "hermit", + "topological", + "##cot", + "drilled", + "subunit", + "francs", + "##layer", + "eel", + "##fk", + "##itas", + "octopus", + "footprint", + "petitions", + "ufo", + "##say", + "##foil", + "interfering", + "leaking", + "palo", + "##metry", + "thistle", + "valiant", + "##pic", + "narayan", + "mcpherson", + "##fast", + "gonzales", + "##ym", + "##enne", + "dustin", + "novgorod", + "solos", + "##zman", + "doin", + "##raph", + "##patient", + "##meyer", + "soluble", + "ashland", + "cuffs", + "carole", + "pendleton", + "whistling", + "vassal", + "##river", + "deviation", + "revisited", + "constituents", + "rallied", + "rotate", + "loomed", + "##eil", + "##nting", + "amateurs", + "augsburg", + "auschwitz", + "crowns", + "skeletons", + "##cona", + "bonnet", + "257", + "dummy", + "globalization", + "simeon", + "sleeper", + "mandal", + "differentiated", + "##crow", + "##mare", + "milne", + "bundled", + "exasperated", + "talmud", + "owes", + "segregated", + "##feng", + "##uary", + "dentist", + "piracy", + "props", + "##rang", + "devlin", + "##torium", + "malicious", + "paws", + "##laid", + "dependency", + "##ergy", + "##fers", + "##enna", + "258", + "pistons", + "rourke", + "jed", + "grammatical", + "tres", + "maha", + "wig", + "512", + "ghostly", + "jayne", + "##achal", + "##creen", + "##ilis", + "##lins", + "##rence", + "designate", + "##with", + "arrogance", + "cambodian", + "clones", + "showdown", + "throttle", + "twain", + "##ception", + "lobes", + "metz", + "nagoya", + "335", + "braking", + "##furt", + "385", + "roaming", + "##minster", + "amin", + "crippled", + "##37", + "##llary", + "indifferent", + "hoffmann", + "idols", + "intimidating", + "1751", + "261", + "influenza", + "memo", + "onions", + "1748", + "bandage", + "consciously", + "##landa", + "##rage", + "clandestine", + "observes", + "swiped", + "tangle", + "##ener", + "##jected", + "##trum", + "##bill", + "##lta", + "hugs", + "congresses", + "josiah", + "spirited", + "##dek", + "humanist", + "managerial", + "filmmaking", + "inmate", + "rhymes", + "debuting", + "grimsby", + "ur", + "##laze", + "duplicate", + "vigor", + "##tf", + "republished", + "bolshevik", + "refurbishment", + "antibiotics", + "martini", + "methane", + "newscasts", + "royale", + "horizons", + "levant", + "iain", + "visas", + "##ischen", + "paler", + "##around", + "manifestation", + "snuck", + "alf", + "chop", + "futile", + "pedestal", + "rehab", + "##kat", + "bmg", + "kerman", + "res", + "fairbanks", + "jarrett", + "abstraction", + "saharan", + "##zek", + "1746", + "procedural", + "clearer", + "kincaid", + "sash", + "luciano", + "##ffey", + "crunch", + "helmut", + "##vara", + "revolutionaries", + "##tute", + "creamy", + "leach", + "##mmon", + "1747", + "permitting", + "nes", + "plight", + "wendell", + "##lese", + "contra", + "ts", + "clancy", + "ipa", + "mach", + "staples", + "autopsy", + "disturbances", + "nueva", + "karin", + "pontiac", + "##uding", + "proxy", + "venerable", + "haunt", + "leto", + "bergman", + "expands", + "##helm", + "wal", + "##pipe", + "canning", + "celine", + "cords", + "obesity", + "##enary", + "intrusion", + "planner", + "##phate", + "reasoned", + "sequencing", + "307", + "harrow", + "##chon", + "##dora", + "marred", + "mcintyre", + "repay", + "tarzan", + "darting", + "248", + "harrisburg", + "margarita", + "repulsed", + "##hur", + "##lding", + "belinda", + "hamburger", + "novo", + "compliant", + "runways", + "bingham", + "registrar", + "skyscraper", + "ic", + "cuthbert", + "improvisation", + "livelihood", + "##corp", + "##elial", + "admiring", + "##dened", + "sporadic", + "believer", + "casablanca", + "popcorn", + "##29", + "asha", + "shovel", + "##bek", + "##dice", + "coiled", + "tangible", + "##dez", + "casper", + "elsie", + "resin", + "tenderness", + "rectory", + "##ivision", + "avail", + "sonar", + "##mori", + "boutique", + "##dier", + "guerre", + "bathed", + "upbringing", + "vaulted", + "sandals", + "blessings", + "##naut", + "##utnant", + "1680", + "306", + "foxes", + "pia", + "corrosion", + "hesitantly", + "confederates", + "crystalline", + "footprints", + "shapiro", + "tirana", + "valentin", + "drones", + "45th", + "microscope", + "shipments", + "texted", + "inquisition", + "wry", + "guernsey", + "unauthorized", + "resigning", + "760", + "ripple", + "schubert", + "stu", + "reassure", + "felony", + "##ardo", + "brittle", + "koreans", + "##havan", + "##ives", + "dun", + "implicit", + "tyres", + "##aldi", + "##lth", + "magnolia", + "##ehan", + "##puri", + "##poulos", + "aggressively", + "fei", + "gr", + "familiarity", + "##poo", + "indicative", + "##trust", + "fundamentally", + "jimmie", + "overrun", + "395", + "anchors", + "moans", + "##opus", + "britannia", + "armagh", + "##ggle", + "purposely", + "seizing", + "##vao", + "bewildered", + "mundane", + "avoidance", + "cosmopolitan", + "geometridae", + "quartermaster", + "caf", + "415", + "chatter", + "engulfed", + "gleam", + "purge", + "##icate", + "juliette", + "jurisprudence", + "guerra", + "revisions", + "##bn", + "casimir", + "brew", + "##jm", + "1749", + "clapton", + "cloudy", + "conde", + "hermitage", + "278", + "simulations", + "torches", + "vincenzo", + "matteo", + "##rill", + "hidalgo", + "booming", + "westbound", + "accomplishment", + "tentacles", + "unaffected", + "##sius", + "annabelle", + "flopped", + "sloping", + "##litz", + "dreamer", + "interceptor", + "vu", + "##loh", + "consecration", + "copying", + "messaging", + "breaker", + "climates", + "hospitalized", + "1752", + "torino", + "afternoons", + "winfield", + "witnessing", + "##teacher", + "breakers", + "choirs", + "sawmill", + "coldly", + "##ege", + "sipping", + "haste", + "uninhabited", + "conical", + "bibliography", + "pamphlets", + "severn", + "edict", + "##oca", + "deux", + "illnesses", + "grips", + "##pl", + "rehearsals", + "sis", + "thinkers", + "tame", + "##keepers", + "1690", + "acacia", + "reformer", + "##osed", + "##rys", + "shuffling", + "##iring", + "##shima", + "eastbound", + "ionic", + "rhea", + "flees", + "littered", + "##oum", + "rocker", + "vomiting", + "groaning", + "champ", + "overwhelmingly", + "civilizations", + "paces", + "sloop", + "adoptive", + "##tish", + "skaters", + "##vres", + "aiding", + "mango", + "##joy", + "nikola", + "shriek", + "##ignon", + "pharmaceuticals", + "##mg", + "tuna", + "calvert", + "gustavo", + "stocked", + "yearbook", + "##urai", + "##mana", + "computed", + "subsp", + "riff", + "hanoi", + "kelvin", + "hamid", + "moors", + "pastures", + "summons", + "jihad", + "nectar", + "##ctors", + "bayou", + "untitled", + "pleasing", + "vastly", + "republics", + "intellect", + "##\u03b7", + "##ulio", + "##tou", + "crumbling", + "stylistic", + "sb", + "##\u06cc", + "consolation", + "frequented", + "h\u2082o", + "walden", + "widows", + "##iens", + "404", + "##ignment", + "chunks", + "improves", + "288", + "grit", + "recited", + "##dev", + "snarl", + "sociological", + "##arte", + "##gul", + "inquired", + "##held", + "bruise", + "clube", + "consultancy", + "homogeneous", + "hornets", + "multiplication", + "pasta", + "prick", + "savior", + "##grin", + "##kou", + "##phile", + "yoon", + "##gara", + "grimes", + "vanishing", + "cheering", + "reacting", + "bn", + "distillery", + "##quisite", + "##vity", + "coe", + "dockyard", + "massif", + "##jord", + "escorts", + "voss", + "##valent", + "byte", + "chopped", + "hawke", + "illusions", + "workings", + "floats", + "##koto", + "##vac", + "kv", + "annapolis", + "madden", + "##onus", + "alvaro", + "noctuidae", + "##cum", + "##scopic", + "avenge", + "steamboat", + "forte", + "illustrates", + "erika", + "##trip", + "570", + "dew", + "nationalities", + "bran", + "manifested", + "thirsty", + "diversified", + "muscled", + "reborn", + "##standing", + "arson", + "##lessness", + "##dran", + "##logram", + "##boys", + "##kushima", + "##vious", + "willoughby", + "##phobia", + "286", + "alsace", + "dashboard", + "yuki", + "##chai", + "granville", + "myspace", + "publicized", + "tricked", + "##gang", + "adjective", + "##ater", + "relic", + "reorganisation", + "enthusiastically", + "indications", + "saxe", + "##lassified", + "consolidate", + "iec", + "padua", + "helplessly", + "ramps", + "renaming", + "regulars", + "pedestrians", + "accents", + "convicts", + "inaccurate", + "lowers", + "mana", + "##pati", + "barrie", + "bjp", + "outta", + "someplace", + "berwick", + "flanking", + "invoked", + "marrow", + "sparsely", + "excerpts", + "clothed", + "rei", + "##ginal", + "wept", + "##stra\u00dfe", + "##vish", + "alexa", + "excel", + "##ptive", + "membranes", + "aquitaine", + "creeks", + "cutler", + "sheppard", + "implementations", + "ns", + "##dur", + "fragrance", + "budge", + "concordia", + "magnesium", + "marcelo", + "##antes", + "gladly", + "vibrating", + "##rral", + "##ggles", + "montrose", + "##omba", + "lew", + "seamus", + "1630", + "cocky", + "##ament", + "##uen", + "bjorn", + "##rrick", + "fielder", + "fluttering", + "##lase", + "methyl", + "kimberley", + "mcdowell", + "reductions", + "barbed", + "##jic", + "##tonic", + "aeronautical", + "condensed", + "distracting", + "##promising", + "huffed", + "##cala", + "##sle", + "claudius", + "invincible", + "missy", + "pious", + "balthazar", + "ci", + "##lang", + "butte", + "combo", + "orson", + "##dication", + "myriad", + "1707", + "silenced", + "##fed", + "##rh", + "coco", + "netball", + "yourselves", + "##oza", + "clarify", + "heller", + "peg", + "durban", + "etudes", + "offender", + "roast", + "blackmail", + "curvature", + "##woods", + "vile", + "309", + "illicit", + "suriname", + "##linson", + "overture", + "1685", + "bubbling", + "gymnast", + "tucking", + "##mming", + "##ouin", + "maldives", + "##bala", + "gurney", + "##dda", + "##eased", + "##oides", + "backside", + "pinto", + "jars", + "racehorse", + "tending", + "##rdial", + "baronetcy", + "wiener", + "duly", + "##rke", + "barbarian", + "cupping", + "flawed", + "##thesis", + "bertha", + "pleistocene", + "puddle", + "swearing", + "##nob", + "##tically", + "fleeting", + "prostate", + "amulet", + "educating", + "##mined", + "##iti", + "##tler", + "75th", + "jens", + "respondents", + "analytics", + "cavaliers", + "papacy", + "raju", + "##iente", + "##ulum", + "##tip", + "funnel", + "271", + "disneyland", + "##lley", + "sociologist", + "##iam", + "2500", + "faulkner", + "louvre", + "menon", + "##dson", + "276", + "##ower", + "afterlife", + "mannheim", + "peptide", + "referees", + "comedians", + "meaningless", + "##anger", + "##laise", + "fabrics", + "hurley", + "renal", + "sleeps", + "##bour", + "##icle", + "breakout", + "kristin", + "roadside", + "animator", + "clover", + "disdain", + "unsafe", + "redesign", + "##urity", + "firth", + "barnsley", + "portage", + "reset", + "narrows", + "268", + "commandos", + "expansive", + "speechless", + "tubular", + "##lux", + "essendon", + "eyelashes", + "smashwords", + "##yad", + "##bang", + "##claim", + "craved", + "sprinted", + "chet", + "somme", + "astor", + "wroc\u0142aw", + "orton", + "266", + "bane", + "##erving", + "##uing", + "mischief", + "##amps", + "##sund", + "scaling", + "terre", + "##xious", + "impairment", + "offenses", + "undermine", + "moi", + "soy", + "contiguous", + "arcadia", + "inuit", + "seam", + "##tops", + "macbeth", + "rebelled", + "##icative", + "##iot", + "590", + "elaborated", + "frs", + "uniformed", + "##dberg", + "259", + "powerless", + "priscilla", + "stimulated", + "980", + "qc", + "arboretum", + "frustrating", + "trieste", + "bullock", + "##nified", + "enriched", + "glistening", + "intern", + "##adia", + "locus", + "nouvelle", + "ollie", + "ike", + "lash", + "starboard", + "ee", + "tapestry", + "headlined", + "hove", + "rigged", + "##vite", + "pollock", + "##yme", + "thrive", + "clustered", + "cas", + "roi", + "gleamed", + "olympiad", + "##lino", + "pressured", + "regimes", + "##hosis", + "##lick", + "ripley", + "##ophone", + "kickoff", + "gallon", + "rockwell", + "##arable", + "crusader", + "glue", + "revolutions", + "scrambling", + "1714", + "grover", + "##jure", + "englishman", + "aztec", + "263", + "contemplating", + "coven", + "ipad", + "preach", + "triumphant", + "tufts", + "##esian", + "rotational", + "##phus", + "328", + "falkland", + "##brates", + "strewn", + "clarissa", + "rejoin", + "environmentally", + "glint", + "banded", + "drenched", + "moat", + "albanians", + "johor", + "rr", + "maestro", + "malley", + "nouveau", + "shaded", + "taxonomy", + "v6", + "adhere", + "bunk", + "airfields", + "##ritan", + "1741", + "encompass", + "remington", + "tran", + "##erative", + "amelie", + "mazda", + "friar", + "morals", + "passions", + "##zai", + "breadth", + "vis", + "##hae", + "argus", + "burnham", + "caressing", + "insider", + "rudd", + "##imov", + "##mini", + "##rso", + "italianate", + "murderous", + "textual", + "wainwright", + "armada", + "bam", + "weave", + "timer", + "##taken", + "##nh", + "fra", + "##crest", + "ardent", + "salazar", + "taps", + "tunis", + "##ntino", + "allegro", + "gland", + "philanthropic", + "##chester", + "implication", + "##optera", + "esq", + "judas", + "noticeably", + "wynn", + "##dara", + "inched", + "indexed", + "crises", + "villiers", + "bandit", + "royalties", + "patterned", + "cupboard", + "interspersed", + "accessory", + "isla", + "kendrick", + "entourage", + "stitches", + "##esthesia", + "headwaters", + "##ior", + "interlude", + "distraught", + "draught", + "1727", + "##basket", + "biased", + "sy", + "transient", + "triad", + "subgenus", + "adapting", + "kidd", + "shortstop", + "##umatic", + "dimly", + "spiked", + "mcleod", + "reprint", + "nellie", + "pretoria", + "windmill", + "##cek", + "singled", + "##mps", + "273", + "reunite", + "##orous", + "747", + "bankers", + "outlying", + "##omp", + "##ports", + "##tream", + "apologies", + "cosmetics", + "patsy", + "##deh", + "##ocks", + "##yson", + "bender", + "nantes", + "serene", + "##nad", + "lucha", + "mmm", + "323", + "##cius", + "##gli", + "cmll", + "coinage", + "nestor", + "juarez", + "##rook", + "smeared", + "sprayed", + "twitching", + "sterile", + "irina", + "embodied", + "juveniles", + "enveloped", + "miscellaneous", + "cancers", + "dq", + "gulped", + "luisa", + "crested", + "swat", + "donegal", + "ref", + "##anov", + "##acker", + "hearst", + "mercantile", + "##lika", + "doorbell", + "ua", + "vicki", + "##alla", + "##som", + "bilbao", + "psychologists", + "stryker", + "sw", + "horsemen", + "turkmenistan", + "wits", + "##national", + "anson", + "mathew", + "screenings", + "##umb", + "rihanna", + "##agne", + "##nessy", + "aisles", + "##iani", + "##osphere", + "hines", + "kenton", + "saskatoon", + "tasha", + "truncated", + "##champ", + "##itan", + "mildred", + "advises", + "fredrik", + "interpreting", + "inhibitors", + "##athi", + "spectroscopy", + "##hab", + "##kong", + "karim", + "panda", + "##oia", + "##nail", + "##vc", + "conqueror", + "kgb", + "leukemia", + "##dity", + "arrivals", + "cheered", + "pisa", + "phosphorus", + "shielded", + "##riated", + "mammal", + "unitarian", + "urgently", + "chopin", + "sanitary", + "##mission", + "spicy", + "drugged", + "hinges", + "##tort", + "tipping", + "trier", + "impoverished", + "westchester", + "##caster", + "267", + "epoch", + "nonstop", + "##gman", + "##khov", + "aromatic", + "centrally", + "cerro", + "##tively", + "##vio", + "billions", + "modulation", + "sedimentary", + "283", + "facilitating", + "outrageous", + "goldstein", + "##eak", + "##kt", + "ld", + "maitland", + "penultimate", + "pollard", + "##dance", + "fleets", + "spaceship", + "vertebrae", + "##nig", + "alcoholism", + "als", + "recital", + "##bham", + "##ference", + "##omics", + "m2", + "##bm", + "trois", + "##tropical", + "##\u0432", + "commemorates", + "##meric", + "marge", + "##raction", + "1643", + "670", + "cosmetic", + "ravaged", + "##ige", + "catastrophe", + "eng", + "##shida", + "albrecht", + "arterial", + "bellamy", + "decor", + "harmon", + "##rde", + "bulbs", + "synchronized", + "vito", + "easiest", + "shetland", + "shielding", + "wnba", + "##glers", + "##ssar", + "##riam", + "brianna", + "cumbria", + "##aceous", + "##rard", + "cores", + "thayer", + "##nsk", + "brood", + "hilltop", + "luminous", + "carts", + "keynote", + "larkin", + "logos", + "##cta", + "##\u0627", + "##mund", + "##quay", + "lilith", + "tinted", + "277", + "wrestle", + "mobilization", + "##uses", + "sequential", + "siam", + "bloomfield", + "takahashi", + "274", + "##ieving", + "presenters", + "ringo", + "blazed", + "witty", + "##oven", + "##ignant", + "devastation", + "haydn", + "harmed", + "newt", + "therese", + "##peed", + "gershwin", + "molina", + "rabbis", + "sudanese", + "001", + "innate", + "restarted", + "##sack", + "##fus", + "slices", + "wb", + "##shah", + "enroll", + "hypothetical", + "hysterical", + "1743", + "fabio", + "indefinite", + "warped", + "##hg", + "exchanging", + "525", + "unsuitable", + "##sboro", + "gallo", + "1603", + "bret", + "cobalt", + "homemade", + "##hunter", + "mx", + "operatives", + "##dhar", + "terraces", + "durable", + "latch", + "pens", + "whorls", + "##ctuated", + "##eaux", + "billing", + "ligament", + "succumbed", + "##gly", + "regulators", + "spawn", + "##brick", + "##stead", + "filmfare", + "rochelle", + "##nzo", + "1725", + "circumstance", + "saber", + "supplements", + "##nsky", + "##tson", + "crowe", + "wellesley", + "carrot", + "##9th", + "##movable", + "primate", + "drury", + "sincerely", + "topical", + "##mad", + "##rao", + "callahan", + "kyiv", + "smarter", + "tits", + "undo", + "##yeh", + "announcements", + "anthologies", + "barrio", + "nebula", + "##islaus", + "##shaft", + "##tyn", + "bodyguards", + "2021", + "assassinate", + "barns", + "emmett", + "scully", + "##mah", + "##yd", + "##eland", + "##tino", + "##itarian", + "demoted", + "gorman", + "lashed", + "prized", + "adventist", + "writ", + "##gui", + "alla", + "invertebrates", + "##ausen", + "1641", + "amman", + "1742", + "align", + "healy", + "redistribution", + "##gf", + "##rize", + "insulation", + "##drop", + "adherents", + "hezbollah", + "vitro", + "ferns", + "yanking", + "269", + "php", + "registering", + "uppsala", + "cheerleading", + "confines", + "mischievous", + "tully", + "##ross", + "49th", + "docked", + "roam", + "stipulated", + "pumpkin", + "##bry", + "prompt", + "##ezer", + "blindly", + "shuddering", + "craftsmen", + "frail", + "scented", + "katharine", + "scramble", + "shaggy", + "sponge", + "helix", + "zaragoza", + "279", + "##52", + "43rd", + "backlash", + "fontaine", + "seizures", + "posse", + "cowan", + "nonfiction", + "telenovela", + "wwii", + "hammered", + "undone", + "##gpur", + "encircled", + "irs", + "##ivation", + "artefacts", + "oneself", + "searing", + "smallpox", + "##belle", + "##osaurus", + "shandong", + "breached", + "upland", + "blushing", + "rankin", + "infinitely", + "psyche", + "tolerated", + "docking", + "evicted", + "##col", + "unmarked", + "##lving", + "gnome", + "lettering", + "litres", + "musique", + "##oint", + "benevolent", + "##jal", + "blackened", + "##anna", + "mccall", + "racers", + "tingle", + "##ocene", + "##orestation", + "introductions", + "radically", + "292", + "##hiff", + "##\u0628\u0627\u062f", + "1610", + "1739", + "munchen", + "plead", + "##nka", + "condo", + "scissors", + "##sight", + "##tens", + "apprehension", + "##cey", + "##yin", + "hallmark", + "watering", + "formulas", + "sequels", + "##llas", + "aggravated", + "bae", + "commencing", + "##building", + "enfield", + "prohibits", + "marne", + "vedic", + "civilized", + "euclidean", + "jagger", + "beforehand", + "blasts", + "dumont", + "##arney", + "##nem", + "740", + "conversions", + "hierarchical", + "rios", + "simulator", + "##dya", + "##lellan", + "hedges", + "oleg", + "thrusts", + "shadowed", + "darby", + "maximize", + "1744", + "gregorian", + "##nded", + "##routed", + "sham", + "unspecified", + "##hog", + "emory", + "factual", + "##smo", + "##tp", + "fooled", + "##rger", + "ortega", + "wellness", + "marlon", + "##oton", + "##urance", + "casket", + "keating", + "ley", + "enclave", + "##ayan", + "char", + "influencing", + "jia", + "##chenko", + "412", + "ammonia", + "erebidae", + "incompatible", + "violins", + "cornered", + "##arat", + "grooves", + "astronauts", + "columbian", + "rampant", + "fabrication", + "kyushu", + "mahmud", + "vanish", + "##dern", + "mesopotamia", + "##lete", + "ict", + "##rgen", + "caspian", + "kenji", + "pitted", + "##vered", + "999", + "grimace", + "roanoke", + "tchaikovsky", + "twinned", + "##analysis", + "##awan", + "xinjiang", + "arias", + "clemson", + "kazakh", + "sizable", + "1662", + "##khand", + "##vard", + "plunge", + "tatum", + "vittorio", + "##nden", + "cholera", + "##dana", + "##oper", + "bracing", + "indifference", + "projectile", + "superliga", + "##chee", + "realises", + "upgrading", + "299", + "porte", + "retribution", + "##vies", + "nk", + "stil", + "##resses", + "ama", + "bureaucracy", + "blackberry", + "bosch", + "testosterone", + "collapses", + "greer", + "##pathic", + "ioc", + "fifties", + "malls", + "##erved", + "bao", + "baskets", + "adolescents", + "siegfried", + "##osity", + "##tosis", + "mantra", + "detecting", + "existent", + "fledgling", + "##cchi", + "dissatisfied", + "gan", + "telecommunication", + "mingled", + "sobbed", + "6000", + "controversies", + "outdated", + "taxis", + "##raus", + "fright", + "slams", + "##lham", + "##fect", + "##tten", + "detectors", + "fetal", + "tanned", + "##uw", + "fray", + "goth", + "olympian", + "skipping", + "mandates", + "scratches", + "sheng", + "unspoken", + "hyundai", + "tracey", + "hotspur", + "restrictive", + "##buch", + "americana", + "mundo", + "##bari", + "burroughs", + "diva", + "vulcan", + "##6th", + "distinctions", + "thumping", + "##ngen", + "mikey", + "sheds", + "fide", + "rescues", + "springsteen", + "vested", + "valuation", + "##ece", + "##ely", + "pinnacle", + "rake", + "sylvie", + "##edo", + "almond", + "quivering", + "##irus", + "alteration", + "faltered", + "##wad", + "51st", + "hydra", + "ticked", + "##kato", + "recommends", + "##dicated", + "antigua", + "arjun", + "stagecoach", + "wilfred", + "trickle", + "pronouns", + "##pon", + "aryan", + "nighttime", + "##anian", + "gall", + "pea", + "stitch", + "##hei", + "leung", + "milos", + "##dini", + "eritrea", + "nexus", + "starved", + "snowfall", + "kant", + "parasitic", + "cot", + "discus", + "hana", + "strikers", + "appleton", + "kitchens", + "##erina", + "##partisan", + "##itha", + "##vius", + "disclose", + "metis", + "##channel", + "1701", + "tesla", + "##vera", + "fitch", + "1735", + "blooded", + "##tila", + "decimal", + "##tang", + "##bai", + "cyclones", + "eun", + "bottled", + "peas", + "pensacola", + "basha", + "bolivian", + "crabs", + "boil", + "lanterns", + "partridge", + "roofed", + "1645", + "necks", + "##phila", + "opined", + "patting", + "##kla", + "##lland", + "chuckles", + "volta", + "whereupon", + "##nche", + "devout", + "euroleague", + "suicidal", + "##dee", + "inherently", + "involuntary", + "knitting", + "nasser", + "##hide", + "puppets", + "colourful", + "courageous", + "southend", + "stills", + "miraculous", + "hodgson", + "richer", + "rochdale", + "ethernet", + "greta", + "uniting", + "prism", + "umm", + "##haya", + "##itical", + "##utation", + "deterioration", + "pointe", + "prowess", + "##ropriation", + "lids", + "scranton", + "billings", + "subcontinent", + "##koff", + "##scope", + "brute", + "kellogg", + "psalms", + "degraded", + "##vez", + "stanis\u0142aw", + "##ructured", + "ferreira", + "pun", + "astonishing", + "gunnar", + "##yat", + "arya", + "prc", + "gottfried", + "##tight", + "excursion", + "##ographer", + "dina", + "##quil", + "##nare", + "huffington", + "illustrious", + "wilbur", + "gundam", + "verandah", + "##zard", + "naacp", + "##odle", + "constructive", + "fjord", + "kade", + "##naud", + "generosity", + "thrilling", + "baseline", + "cayman", + "frankish", + "plastics", + "accommodations", + "zoological", + "##fting", + "cedric", + "qb", + "motorized", + "##dome", + "##otted", + "squealed", + "tackled", + "canucks", + "budgets", + "situ", + "asthma", + "dail", + "gabled", + "grasslands", + "whimpered", + "writhing", + "judgments", + "##65", + "minnie", + "pv", + "##carbon", + "bananas", + "grille", + "domes", + "monique", + "odin", + "maguire", + "markham", + "tierney", + "##estra", + "##chua", + "libel", + "poke", + "speedy", + "atrium", + "laval", + "notwithstanding", + "##edly", + "fai", + "kala", + "##sur", + "robb", + "##sma", + "listings", + "luz", + "supplementary", + "tianjin", + "##acing", + "enzo", + "jd", + "ric", + "scanner", + "croats", + "transcribed", + "##49", + "arden", + "cv", + "##hair", + "##raphy", + "##lver", + "##uy", + "357", + "seventies", + "staggering", + "alam", + "horticultural", + "hs", + "regression", + "timbers", + "blasting", + "##ounded", + "montagu", + "manipulating", + "##cit", + "catalytic", + "1550", + "troopers", + "##meo", + "condemnation", + "fitzpatrick", + "##oire", + "##roved", + "inexperienced", + "1670", + "castes", + "##lative", + "outing", + "314", + "dubois", + "flicking", + "quarrel", + "ste", + "learners", + "1625", + "iq", + "whistled", + "##class", + "282", + "classify", + "tariffs", + "temperament", + "355", + "folly", + "liszt", + "##yles", + "immersed", + "jordanian", + "ceasefire", + "apparel", + "extras", + "maru", + "fished", + "##bio", + "harta", + "stockport", + "assortment", + "craftsman", + "paralysis", + "transmitters", + "##cola", + "blindness", + "##wk", + "fatally", + "proficiency", + "solemnly", + "##orno", + "repairing", + "amore", + "groceries", + "ultraviolet", + "##chase", + "schoolhouse", + "##tua", + "resurgence", + "nailed", + "##otype", + "##\u00d7", + "ruse", + "saliva", + "diagrams", + "##tructing", + "albans", + "rann", + "thirties", + "1b", + "antennas", + "hilarious", + "cougars", + "paddington", + "stats", + "##eger", + "breakaway", + "ipod", + "reza", + "authorship", + "prohibiting", + "scoffed", + "##etz", + "##ttle", + "conscription", + "defected", + "trondheim", + "##fires", + "ivanov", + "keenan", + "##adan", + "##ciful", + "##fb", + "##slow", + "locating", + "##ials", + "##tford", + "cadiz", + "basalt", + "blankly", + "interned", + "rags", + "rattling", + "##tick", + "carpathian", + "reassured", + "sync", + "bum", + "guildford", + "iss", + "staunch", + "##onga", + "astronomers", + "sera", + "sofie", + "emergencies", + "susquehanna", + "##heard", + "duc", + "mastery", + "vh1", + "williamsburg", + "bayer", + "buckled", + "craving", + "##khan", + "##rdes", + "bloomington", + "##write", + "alton", + "barbecue", + "##bians", + "justine", + "##hri", + "##ndt", + "delightful", + "smartphone", + "newtown", + "photon", + "retrieval", + "peugeot", + "hissing", + "##monium", + "##orough", + "flavors", + "lighted", + "relaunched", + "tainted", + "##games", + "##lysis", + "anarchy", + "microscopic", + "hopping", + "adept", + "evade", + "evie", + "##beau", + "inhibit", + "sinn", + "adjustable", + "hurst", + "intuition", + "wilton", + "cisco", + "44th", + "lawful", + "lowlands", + "stockings", + "thierry", + "##dalen", + "##hila", + "##nai", + "fates", + "prank", + "tb", + "maison", + "lobbied", + "provocative", + "1724", + "4a", + "utopia", + "##qual", + "carbonate", + "gujarati", + "purcell", + "##rford", + "curtiss", + "##mei", + "overgrown", + "arenas", + "mediation", + "swallows", + "##rnik", + "respectful", + "turnbull", + "##hedron", + "##hope", + "alyssa", + "ozone", + "##\u02bbi", + "ami", + "gestapo", + "johansson", + "snooker", + "canteen", + "cuff", + "declines", + "empathy", + "stigma", + "##ags", + "##iner", + "##raine", + "taxpayers", + "gui", + "volga", + "##wright", + "##copic", + "lifespan", + "overcame", + "tattooed", + "enactment", + "giggles", + "##ador", + "##camp", + "barrington", + "bribe", + "obligatory", + "orbiting", + "peng", + "##enas", + "elusive", + "sucker", + "##vating", + "cong", + "hardship", + "empowered", + "anticipating", + "estrada", + "cryptic", + "greasy", + "detainees", + "planck", + "sudbury", + "plaid", + "dod", + "marriott", + "kayla", + "##ears", + "##vb", + "##zd", + "mortally", + "##hein", + "cognition", + "radha", + "319", + "liechtenstein", + "meade", + "richly", + "argyle", + "harpsichord", + "liberalism", + "trumpets", + "lauded", + "tyrant", + "salsa", + "tiled", + "lear", + "promoters", + "reused", + "slicing", + "trident", + "##chuk", + "##gami", + "##lka", + "cantor", + "checkpoint", + "##points", + "gaul", + "leger", + "mammalian", + "##tov", + "##aar", + "##schaft", + "doha", + "frenchman", + "nirvana", + "##vino", + "delgado", + "headlining", + "##eron", + "##iography", + "jug", + "tko", + "1649", + "naga", + "intersections", + "##jia", + "benfica", + "nawab", + "##suka", + "ashford", + "gulp", + "##deck", + "##vill", + "##rug", + "brentford", + "frazier", + "pleasures", + "dunne", + "potsdam", + "shenzhen", + "dentistry", + "##tec", + "flanagan", + "##dorff", + "##hear", + "chorale", + "dinah", + "prem", + "quezon", + "##rogated", + "relinquished", + "sutra", + "terri", + "##pani", + "flaps", + "##rissa", + "poly", + "##rnet", + "homme", + "aback", + "##eki", + "linger", + "womb", + "##kson", + "##lewood", + "doorstep", + "orthodoxy", + "threaded", + "westfield", + "##rval", + "dioceses", + "fridays", + "subsided", + "##gata", + "loyalists", + "##biotic", + "##ettes", + "letterman", + "lunatic", + "prelate", + "tenderly", + "invariably", + "souza", + "thug", + "winslow", + "##otide", + "furlongs", + "gogh", + "jeopardy", + "##runa", + "pegasus", + "##umble", + "humiliated", + "standalone", + "tagged", + "##roller", + "freshmen", + "klan", + "##bright", + "attaining", + "initiating", + "transatlantic", + "logged", + "viz", + "##uance", + "1723", + "combatants", + "intervening", + "stephane", + "chieftain", + "despised", + "grazed", + "317", + "cdc", + "galveston", + "godzilla", + "macro", + "simulate", + "##planes", + "parades", + "##esses", + "960", + "##ductive", + "##unes", + "equator", + "overdose", + "##cans", + "##hosh", + "##lifting", + "joshi", + "epstein", + "sonora", + "treacherous", + "aquatics", + "manchu", + "responsive", + "##sation", + "supervisory", + "##christ", + "##llins", + "##ibar", + "##balance", + "##uso", + "kimball", + "karlsruhe", + "mab", + "##emy", + "ignores", + "phonetic", + "reuters", + "spaghetti", + "820", + "almighty", + "danzig", + "rumbling", + "tombstone", + "designations", + "lured", + "outset", + "##felt", + "supermarkets", + "##wt", + "grupo", + "kei", + "kraft", + "susanna", + "##blood", + "comprehension", + "genealogy", + "##aghan", + "##verted", + "redding", + "##ythe", + "1722", + "bowing", + "##pore", + "##roi", + "lest", + "sharpened", + "fulbright", + "valkyrie", + "sikhs", + "##unds", + "swans", + "bouquet", + "merritt", + "##tage", + "##venting", + "commuted", + "redhead", + "clerks", + "leasing", + "cesare", + "dea", + "hazy", + "##vances", + "fledged", + "greenfield", + "servicemen", + "##gical", + "armando", + "blackout", + "dt", + "sagged", + "downloadable", + "intra", + "potion", + "pods", + "##4th", + "##mism", + "xp", + "attendants", + "gambia", + "stale", + "##ntine", + "plump", + "asteroids", + "rediscovered", + "buds", + "flea", + "hive", + "##neas", + "1737", + "classifications", + "debuts", + "##eles", + "olympus", + "scala", + "##eurs", + "##gno", + "##mute", + "hummed", + "sigismund", + "visuals", + "wiggled", + "await", + "pilasters", + "clench", + "sulfate", + "##ances", + "bellevue", + "enigma", + "trainee", + "snort", + "##sw", + "clouded", + "denim", + "##rank", + "##rder", + "churning", + "hartman", + "lodges", + "riches", + "sima", + "##missible", + "accountable", + "socrates", + "regulates", + "mueller", + "##cr", + "1702", + "avoids", + "solids", + "himalayas", + "nutrient", + "pup", + "##jevic", + "squat", + "fades", + "nec", + "##lates", + "##pina", + "##rona", + "##\u03bf\u03c5", + "privateer", + "tequila", + "##gative", + "##mpton", + "apt", + "hornet", + "immortals", + "##dou", + "asturias", + "cleansing", + "dario", + "##rries", + "##anta", + "etymology", + "servicing", + "zhejiang", + "##venor", + "##nx", + "horned", + "erasmus", + "rayon", + "relocating", + "\u00a310", + "##bags", + "escalated", + "promenade", + "stubble", + "2010s", + "artisans", + "axial", + "liquids", + "mora", + "sho", + "yoo", + "##tsky", + "bundles", + "oldies", + "##nally", + "notification", + "bastion", + "##ths", + "sparkle", + "##lved", + "1728", + "leash", + "pathogen", + "highs", + "##hmi", + "immature", + "880", + "gonzaga", + "ignatius", + "mansions", + "monterrey", + "sweets", + "bryson", + "##loe", + "polled", + "regatta", + "brightest", + "pei", + "rosy", + "squid", + "hatfield", + "payroll", + "addict", + "meath", + "cornerback", + "heaviest", + "lodging", + "##mage", + "capcom", + "rippled", + "##sily", + "barnet", + "mayhem", + "ymca", + "snuggled", + "rousseau", + "##cute", + "blanchard", + "284", + "fragmented", + "leighton", + "chromosomes", + "risking", + "##md", + "##strel", + "##utter", + "corinne", + "coyotes", + "cynical", + "hiroshi", + "yeomanry", + "##ractive", + "ebook", + "grading", + "mandela", + "plume", + "agustin", + "magdalene", + "##rkin", + "bea", + "femme", + "trafford", + "##coll", + "##lun", + "##tance", + "52nd", + "fourier", + "upton", + "##mental", + "camilla", + "gust", + "iihf", + "islamabad", + "longevity", + "##kala", + "feldman", + "netting", + "##rization", + "endeavour", + "foraging", + "mfa", + "orr", + "##open", + "greyish", + "contradiction", + "graz", + "##ruff", + "handicapped", + "marlene", + "tweed", + "oaxaca", + "spp", + "campos", + "miocene", + "pri", + "configured", + "cooks", + "pluto", + "cozy", + "pornographic", + "##entes", + "70th", + "fairness", + "glided", + "jonny", + "lynne", + "rounding", + "sired", + "##emon", + "##nist", + "remade", + "uncover", + "##mack", + "complied", + "lei", + "newsweek", + "##jured", + "##parts", + "##enting", + "##pg", + "293", + "finer", + "guerrillas", + "athenian", + "deng", + "disused", + "stepmother", + "accuse", + "gingerly", + "seduction", + "521", + "confronting", + "##walker", + "##going", + "gora", + "nostalgia", + "sabres", + "virginity", + "wrenched", + "##minated", + "syndication", + "wielding", + "eyre", + "##56", + "##gnon", + "##igny", + "behaved", + "taxpayer", + "sweeps", + "##growth", + "childless", + "gallant", + "##ywood", + "amplified", + "geraldine", + "scrape", + "##ffi", + "babylonian", + "fresco", + "##rdan", + "##kney", + "##position", + "1718", + "restricting", + "tack", + "fukuoka", + "osborn", + "selector", + "partnering", + "##dlow", + "318", + "gnu", + "kia", + "tak", + "whitley", + "gables", + "##54", + "##mania", + "mri", + "softness", + "immersion", + "##bots", + "##evsky", + "1713", + "chilling", + "insignificant", + "pcs", + "##uis", + "elites", + "lina", + "purported", + "supplemental", + "teaming", + "##americana", + "##dding", + "##inton", + "proficient", + "rouen", + "##nage", + "##rret", + "niccolo", + "selects", + "##bread", + "fluffy", + "1621", + "gruff", + "knotted", + "mukherjee", + "polgara", + "thrash", + "nicholls", + "secluded", + "smoothing", + "thru", + "corsica", + "loaf", + "whitaker", + "inquiries", + "##rrier", + "##kam", + "indochina", + "289", + "marlins", + "myles", + "peking", + "##tea", + "extracts", + "pastry", + "superhuman", + "connacht", + "vogel", + "##ditional", + "##het", + "##udged", + "##lash", + "gloss", + "quarries", + "refit", + "teaser", + "##alic", + "##gaon", + "20s", + "materialized", + "sling", + "camped", + "pickering", + "tung", + "tracker", + "pursuant", + "##cide", + "cranes", + "soc", + "##cini", + "##typical", + "##viere", + "anhalt", + "overboard", + "workout", + "chores", + "fares", + "orphaned", + "stains", + "##logie", + "fenton", + "surpassing", + "joyah", + "triggers", + "##itte", + "grandmaster", + "##lass", + "##lists", + "clapping", + "fraudulent", + "ledger", + "nagasaki", + "##cor", + "##nosis", + "##tsa", + "eucalyptus", + "tun", + "##icio", + "##rney", + "##tara", + "dax", + "heroism", + "ina", + "wrexham", + "onboard", + "unsigned", + "##dates", + "moshe", + "galley", + "winnie", + "droplets", + "exiles", + "praises", + "watered", + "noodles", + "##aia", + "fein", + "adi", + "leland", + "multicultural", + "stink", + "bingo", + "comets", + "erskine", + "modernized", + "canned", + "constraint", + "domestically", + "chemotherapy", + "featherweight", + "stifled", + "##mum", + "darkly", + "irresistible", + "refreshing", + "hasty", + "isolate", + "##oys", + "kitchener", + "planners", + "##wehr", + "cages", + "yarn", + "implant", + "toulon", + "elects", + "childbirth", + "yue", + "##lind", + "##lone", + "cn", + "rightful", + "sportsman", + "junctions", + "remodeled", + "specifies", + "##rgh", + "291", + "##oons", + "complimented", + "##urgent", + "lister", + "ot", + "##logic", + "bequeathed", + "cheekbones", + "fontana", + "gabby", + "##dial", + "amadeus", + "corrugated", + "maverick", + "resented", + "triangles", + "##hered", + "##usly", + "nazareth", + "tyrol", + "1675", + "assent", + "poorer", + "sectional", + "aegean", + "##cous", + "296", + "nylon", + "ghanaian", + "##egorical", + "##weig", + "cushions", + "forbid", + "fusiliers", + "obstruction", + "somerville", + "##scia", + "dime", + "earrings", + "elliptical", + "leyte", + "oder", + "polymers", + "timmy", + "atm", + "midtown", + "piloted", + "settles", + "continual", + "externally", + "mayfield", + "##uh", + "enrichment", + "henson", + "keane", + "persians", + "1733", + "benji", + "braden", + "pep", + "324", + "##efe", + "contenders", + "pepsi", + "valet", + "##isches", + "298", + "##asse", + "##earing", + "goofy", + "stroll", + "##amen", + "authoritarian", + "occurrences", + "adversary", + "ahmedabad", + "tangent", + "toppled", + "dorchester", + "1672", + "modernism", + "marxism", + "islamist", + "charlemagne", + "exponential", + "racks", + "unicode", + "brunette", + "mbc", + "pic", + "skirmish", + "##bund", + "##lad", + "##powered", + "##yst", + "hoisted", + "messina", + "shatter", + "##ctum", + "jedi", + "vantage", + "##music", + "##neil", + "clemens", + "mahmoud", + "corrupted", + "authentication", + "lowry", + "nils", + "##washed", + "omnibus", + "wounding", + "jillian", + "##itors", + "##opped", + "serialized", + "narcotics", + "handheld", + "##arm", + "##plicity", + "intersecting", + "stimulating", + "##onis", + "crate", + "fellowships", + "hemingway", + "casinos", + "climatic", + "fordham", + "copeland", + "drip", + "beatty", + "leaflets", + "robber", + "brothel", + "madeira", + "##hedral", + "sphinx", + "ultrasound", + "##vana", + "valor", + "forbade", + "leonid", + "villas", + "##aldo", + "duane", + "marquez", + "##cytes", + "disadvantaged", + "forearms", + "kawasaki", + "reacts", + "consular", + "lax", + "uncles", + "uphold", + "##hopper", + "concepcion", + "dorsey", + "lass", + "##izan", + "arching", + "passageway", + "1708", + "researches", + "tia", + "internationals", + "##graphs", + "##opers", + "distinguishes", + "javanese", + "divert", + "##uven", + "plotted", + "##listic", + "##rwin", + "##erik", + "##tify", + "affirmative", + "signifies", + "validation", + "##bson", + "kari", + "felicity", + "georgina", + "zulu", + "##eros", + "##rained", + "##rath", + "overcoming", + "##dot", + "argyll", + "##rbin", + "1734", + "chiba", + "ratification", + "windy", + "earls", + "parapet", + "##marks", + "hunan", + "pristine", + "astrid", + "punta", + "##gart", + "brodie", + "##kota", + "##oder", + "malaga", + "minerva", + "rouse", + "##phonic", + "bellowed", + "pagoda", + "portals", + "reclamation", + "##gur", + "##odies", + "##\u2044\u2084", + "parentheses", + "quoting", + "allergic", + "palette", + "showcases", + "benefactor", + "heartland", + "nonlinear", + "##tness", + "bladed", + "cheerfully", + "scans", + "##ety", + "##hone", + "1666", + "girlfriends", + "pedersen", + "hiram", + "sous", + "##liche", + "##nator", + "1683", + "##nery", + "##orio", + "##umen", + "bobo", + "primaries", + "smiley", + "##cb", + "unearthed", + "uniformly", + "fis", + "metadata", + "1635", + "ind", + "##oted", + "recoil", + "##titles", + "##tura", + "##\u03b9\u03b1", + "406", + "hilbert", + "jamestown", + "mcmillan", + "tulane", + "seychelles", + "##frid", + "antics", + "coli", + "fated", + "stucco", + "##grants", + "1654", + "bulky", + "accolades", + "arrays", + "caledonian", + "carnage", + "optimism", + "puebla", + "##tative", + "##cave", + "enforcing", + "rotherham", + "seo", + "dunlop", + "aeronautics", + "chimed", + "incline", + "zoning", + "archduke", + "hellenistic", + "##oses", + "##sions", + "candi", + "thong", + "##ople", + "magnate", + "rustic", + "##rsk", + "projective", + "slant", + "##offs", + "danes", + "hollis", + "vocalists", + "##ammed", + "congenital", + "contend", + "gesellschaft", + "##ocating", + "##pressive", + "douglass", + "quieter", + "##cm", + "##kshi", + "howled", + "salim", + "spontaneously", + "townsville", + "buena", + "southport", + "##bold", + "kato", + "1638", + "faerie", + "stiffly", + "##vus", + "##rled", + "297", + "flawless", + "realising", + "taboo", + "##7th", + "bytes", + "straightening", + "356", + "jena", + "##hid", + "##rmin", + "cartwright", + "berber", + "bertram", + "soloists", + "411", + "noses", + "417", + "coping", + "fission", + "hardin", + "inca", + "##cen", + "1717", + "mobilized", + "vhf", + "##raf", + "biscuits", + "curate", + "##85", + "##anial", + "331", + "gaunt", + "neighbourhoods", + "1540", + "##abas", + "blanca", + "bypassed", + "sockets", + "behold", + "coincidentally", + "##bane", + "nara", + "shave", + "splinter", + "terrific", + "##arion", + "##erian", + "commonplace", + "juris", + "redwood", + "waistband", + "boxed", + "caitlin", + "fingerprints", + "jennie", + "naturalized", + "##ired", + "balfour", + "craters", + "jody", + "bungalow", + "hugely", + "quilt", + "glitter", + "pigeons", + "undertaker", + "bulging", + "constrained", + "goo", + "##sil", + "##akh", + "assimilation", + "reworked", + "##person", + "persuasion", + "##pants", + "felicia", + "##cliff", + "##ulent", + "1732", + "explodes", + "##dun", + "##inium", + "##zic", + "lyman", + "vulture", + "hog", + "overlook", + "begs", + "northwards", + "ow", + "spoil", + "##urer", + "fatima", + "favorably", + "accumulate", + "sargent", + "sorority", + "corresponded", + "dispersal", + "kochi", + "toned", + "##imi", + "##lita", + "internacional", + "newfound", + "##agger", + "##lynn", + "##rigue", + "booths", + "peanuts", + "##eborg", + "medicare", + "muriel", + "nur", + "##uram", + "crates", + "millennia", + "pajamas", + "worsened", + "##breakers", + "jimi", + "vanuatu", + "yawned", + "##udeau", + "carousel", + "##hony", + "hurdle", + "##ccus", + "##mounted", + "##pod", + "rv", + "##eche", + "airship", + "ambiguity", + "compulsion", + "recapture", + "##claiming", + "arthritis", + "##osomal", + "1667", + "asserting", + "ngc", + "sniffing", + "dade", + "discontent", + "glendale", + "ported", + "##amina", + "defamation", + "rammed", + "##scent", + "fling", + "livingstone", + "##fleet", + "875", + "##ppy", + "apocalyptic", + "comrade", + "lcd", + "##lowe", + "cessna", + "eine", + "persecuted", + "subsistence", + "demi", + "hoop", + "reliefs", + "710", + "coptic", + "progressing", + "stemmed", + "perpetrators", + "1665", + "priestess", + "##nio", + "dobson", + "ebony", + "rooster", + "itf", + "tortricidae", + "##bbon", + "##jian", + "cleanup", + "##jean", + "##\u00f8y", + "1721", + "eighties", + "taxonomic", + "holiness", + "##hearted", + "##spar", + "antilles", + "showcasing", + "stabilized", + "##nb", + "gia", + "mascara", + "michelangelo", + "dawned", + "##uria", + "##vinsky", + "extinguished", + "fitz", + "grotesque", + "\u00a3100", + "##fera", + "##loid", + "##mous", + "barges", + "neue", + "throbbed", + "cipher", + "johnnie", + "##a1", + "##mpt", + "outburst", + "##swick", + "spearheaded", + "administrations", + "c1", + "heartbreak", + "pixels", + "pleasantly", + "##enay", + "lombardy", + "plush", + "##nsed", + "bobbie", + "##hly", + "reapers", + "tremor", + "xiang", + "minogue", + "substantive", + "hitch", + "barak", + "##wyl", + "kwan", + "##encia", + "910", + "obscene", + "elegance", + "indus", + "surfer", + "bribery", + "conserve", + "##hyllum", + "##masters", + "horatio", + "##fat", + "apes", + "rebound", + "psychotic", + "##pour", + "iteration", + "##mium", + "##vani", + "botanic", + "horribly", + "antiques", + "dispose", + "paxton", + "##hli", + "##wg", + "timeless", + "1704", + "disregard", + "engraver", + "hounds", + "##bau", + "##version", + "looted", + "uno", + "facilitates", + "groans", + "masjid", + "rutland", + "antibody", + "disqualification", + "decatur", + "footballers", + "quake", + "slacks", + "48th", + "rein", + "scribe", + "stabilize", + "commits", + "exemplary", + "tho", + "##hort", + "##chison", + "pantry", + "traversed", + "##hiti", + "disrepair", + "identifiable", + "vibrated", + "baccalaureate", + "##nnis", + "csa", + "interviewing", + "##iensis", + "##ra\u00dfe", + "greaves", + "wealthiest", + "343", + "classed", + "jogged", + "\u00a35", + "##58", + "##atal", + "illuminating", + "knicks", + "respecting", + "##uno", + "scrubbed", + "##iji", + "##dles", + "kruger", + "moods", + "growls", + "raider", + "silvia", + "chefs", + "kam", + "vr", + "cree", + "percival", + "##terol", + "gunter", + "counterattack", + "defiant", + "henan", + "ze", + "##rasia", + "##riety", + "equivalence", + "submissions", + "##fra", + "##thor", + "bautista", + "mechanically", + "##heater", + "cornice", + "herbal", + "templar", + "##mering", + "outputs", + "ruining", + "ligand", + "renumbered", + "extravagant", + "mika", + "blockbuster", + "eta", + "insurrection", + "##ilia", + "darkening", + "ferocious", + "pianos", + "strife", + "kinship", + "##aer", + "melee", + "##anor", + "##iste", + "##may", + "##oue", + "decidedly", + "weep", + "##jad", + "##missive", + "##ppel", + "354", + "puget", + "unease", + "##gnant", + "1629", + "hammering", + "kassel", + "ob", + "wessex", + "##lga", + "bromwich", + "egan", + "paranoia", + "utilization", + "##atable", + "##idad", + "contradictory", + "provoke", + "##ols", + "##ouring", + "##tangled", + "knesset", + "##very", + "##lette", + "plumbing", + "##sden", + "##\u00b9", + "greensboro", + "occult", + "sniff", + "338", + "zev", + "beaming", + "gamer", + "haggard", + "mahal", + "##olt", + "##pins", + "mendes", + "utmost", + "briefing", + "gunnery", + "##gut", + "##pher", + "##zh", + "##rok", + "1679", + "khalifa", + "sonya", + "##boot", + "principals", + "urbana", + "wiring", + "##liffe", + "##minating", + "##rrado", + "dahl", + "nyu", + "skepticism", + "np", + "townspeople", + "ithaca", + "lobster", + "somethin", + "##fur", + "##arina", + "##\u22121", + "freighter", + "zimmerman", + "biceps", + "contractual", + "##herton", + "amend", + "hurrying", + "subconscious", + "##anal", + "336", + "meng", + "clermont", + "spawning", + "##eia", + "##lub", + "dignitaries", + "impetus", + "snacks", + "spotting", + "twigs", + "##bilis", + "##cz", + "##ouk", + "libertadores", + "nic", + "skylar", + "##aina", + "##firm", + "gustave", + "asean", + "##anum", + "dieter", + "legislatures", + "flirt", + "bromley", + "trolls", + "umar", + "##bbies", + "##tyle", + "blah", + "parc", + "bridgeport", + "crank", + "negligence", + "##nction", + "46th", + "constantin", + "molded", + "bandages", + "seriousness", + "00pm", + "siegel", + "carpets", + "compartments", + "upbeat", + "statehood", + "##dner", + "##edging", + "marko", + "730", + "platt", + "##hane", + "paving", + "##iy", + "1738", + "abbess", + "impatience", + "limousine", + "nbl", + "##talk", + "441", + "lucille", + "mojo", + "nightfall", + "robbers", + "##nais", + "karel", + "brisk", + "calves", + "replicate", + "ascribed", + "telescopes", + "##olf", + "intimidated", + "##reen", + "ballast", + "specialization", + "##sit", + "aerodynamic", + "caliphate", + "rainer", + "visionary", + "##arded", + "epsilon", + "##aday", + "##onte", + "aggregation", + "auditory", + "boosted", + "reunification", + "kathmandu", + "loco", + "robyn", + "402", + "acknowledges", + "appointing", + "humanoid", + "newell", + "redeveloped", + "restraints", + "##tained", + "barbarians", + "chopper", + "1609", + "italiana", + "##lez", + "##lho", + "investigates", + "wrestlemania", + "##anies", + "##bib", + "690", + "##falls", + "creaked", + "dragoons", + "gravely", + "minions", + "stupidity", + "volley", + "##harat", + "##week", + "musik", + "##eries", + "##uously", + "fungal", + "massimo", + "semantics", + "malvern", + "##ahl", + "##pee", + "discourage", + "embryo", + "imperialism", + "1910s", + "profoundly", + "##ddled", + "jiangsu", + "sparkled", + "stat", + "##holz", + "sweatshirt", + "tobin", + "##iction", + "sneered", + "##cheon", + "##oit", + "brit", + "causal", + "smyth", + "##neuve", + "diffuse", + "perrin", + "silvio", + "##ipes", + "##recht", + "detonated", + "iqbal", + "selma", + "##nism", + "##zumi", + "roasted", + "##riders", + "tay", + "##ados", + "##mament", + "##mut", + "##rud", + "840", + "completes", + "nipples", + "cfa", + "flavour", + "hirsch", + "##laus", + "calderon", + "sneakers", + "moravian", + "##ksha", + "1622", + "rq", + "294", + "##imeters", + "bodo", + "##isance", + "##pre", + "##ronia", + "anatomical", + "excerpt", + "##lke", + "dh", + "kunst", + "##tablished", + "##scoe", + "biomass", + "panted", + "unharmed", + "gael", + "housemates", + "montpellier", + "##59", + "coa", + "rodents", + "tonic", + "hickory", + "singleton", + "##taro", + "451", + "1719", + "aldo", + "breaststroke", + "dempsey", + "och", + "rocco", + "##cuit", + "merton", + "dissemination", + "midsummer", + "serials", + "##idi", + "haji", + "polynomials", + "##rdon", + "gs", + "enoch", + "prematurely", + "shutter", + "taunton", + "\u00a33", + "##grating", + "##inates", + "archangel", + "harassed", + "##asco", + "326", + "archway", + "dazzling", + "##ecin", + "1736", + "sumo", + "wat", + "##kovich", + "1086", + "honneur", + "##ently", + "##nostic", + "##ttal", + "##idon", + "1605", + "403", + "1716", + "blogger", + "rents", + "##gnan", + "hires", + "##ikh", + "##dant", + "howie", + "##rons", + "handler", + "retracted", + "shocks", + "1632", + "arun", + "duluth", + "kepler", + "trumpeter", + "##lary", + "peeking", + "seasoned", + "trooper", + "##mara", + "laszlo", + "##iciencies", + "##rti", + "heterosexual", + "##inatory", + "##ssion", + "indira", + "jogging", + "##inga", + "##lism", + "beit", + "dissatisfaction", + "malice", + "##ately", + "nedra", + "peeling", + "##rgeon", + "47th", + "stadiums", + "475", + "vertigo", + "##ains", + "iced", + "restroom", + "##plify", + "##tub", + "illustrating", + "pear", + "##chner", + "##sibility", + "inorganic", + "rappers", + "receipts", + "watery", + "##kura", + "lucinda", + "##oulos", + "reintroduced", + "##8th", + "##tched", + "gracefully", + "saxons", + "nutritional", + "wastewater", + "rained", + "favourites", + "bedrock", + "fisted", + "hallways", + "likeness", + "upscale", + "##lateral", + "1580", + "blinds", + "prequel", + "##pps", + "##tama", + "deter", + "humiliating", + "restraining", + "tn", + "vents", + "1659", + "laundering", + "recess", + "rosary", + "tractors", + "coulter", + "federer", + "##ifiers", + "##plin", + "persistence", + "##quitable", + "geschichte", + "pendulum", + "quakers", + "##beam", + "bassett", + "pictorial", + "buffet", + "koln", + "##sitor", + "drills", + "reciprocal", + "shooters", + "##57", + "##cton", + "##tees", + "converge", + "pip", + "dmitri", + "donnelly", + "yamamoto", + "aqua", + "azores", + "demographics", + "hypnotic", + "spitfire", + "suspend", + "wryly", + "roderick", + "##rran", + "sebastien", + "##asurable", + "mavericks", + "##fles", + "##200", + "himalayan", + "prodigy", + "##iance", + "transvaal", + "demonstrators", + "handcuffs", + "dodged", + "mcnamara", + "sublime", + "1726", + "crazed", + "##efined", + "##till", + "ivo", + "pondered", + "reconciled", + "shrill", + "sava", + "##duk", + "bal", + "cad", + "heresy", + "jaipur", + "goran", + "##nished", + "341", + "lux", + "shelly", + "whitehall", + "##hre", + "israelis", + "peacekeeping", + "##wled", + "1703", + "demetrius", + "ousted", + "##arians", + "##zos", + "beale", + "anwar", + "backstroke", + "raged", + "shrinking", + "cremated", + "##yck", + "benign", + "towing", + "wadi", + "darmstadt", + "landfill", + "parana", + "soothe", + "colleen", + "sidewalks", + "mayfair", + "tumble", + "hepatitis", + "ferrer", + "superstructure", + "##gingly", + "##urse", + "##wee", + "anthropological", + "translators", + "##mies", + "closeness", + "hooves", + "##pw", + "mondays", + "##roll", + "##vita", + "landscaping", + "##urized", + "purification", + "sock", + "thorns", + "thwarted", + "jalan", + "tiberius", + "##taka", + "saline", + "##rito", + "confidently", + "khyber", + "sculptors", + "##ij", + "brahms", + "hammersmith", + "inspectors", + "battista", + "fivb", + "fragmentation", + "hackney", + "##uls", + "arresting", + "exercising", + "antoinette", + "bedfordshire", + "##zily", + "dyed", + "##hema", + "1656", + "racetrack", + "variability", + "##tique", + "1655", + "austrians", + "deteriorating", + "madman", + "theorists", + "aix", + "lehman", + "weathered", + "1731", + "decreed", + "eruptions", + "1729", + "flaw", + "quinlan", + "sorbonne", + "flutes", + "nunez", + "1711", + "adored", + "downwards", + "fable", + "rasped", + "1712", + "moritz", + "mouthful", + "renegade", + "shivers", + "stunts", + "dysfunction", + "restrain", + "translit", + "327", + "pancakes", + "##avio", + "##cision", + "##tray", + "351", + "vial", + "##lden", + "bain", + "##maid", + "##oxide", + "chihuahua", + "malacca", + "vimes", + "##rba", + "##rnier", + "1664", + "donnie", + "plaques", + "##ually", + "337", + "bangs", + "floppy", + "huntsville", + "loretta", + "nikolay", + "##otte", + "eater", + "handgun", + "ubiquitous", + "##hett", + "eras", + "zodiac", + "1634", + "##omorphic", + "1820s", + "##zog", + "cochran", + "##bula", + "##lithic", + "warring", + "##rada", + "dalai", + "excused", + "blazers", + "mcconnell", + "reeling", + "bot", + "este", + "##abi", + "geese", + "hoax", + "taxon", + "##bla", + "guitarists", + "##icon", + "condemning", + "hunts", + "inversion", + "moffat", + "taekwondo", + "##lvis", + "1624", + "stammered", + "##rest", + "##rzy", + "sousa", + "fundraiser", + "marylebone", + "navigable", + "uptown", + "cabbage", + "daniela", + "salman", + "shitty", + "whimper", + "##kian", + "##utive", + "programmers", + "protections", + "rm", + "##rmi", + "##rued", + "forceful", + "##enes", + "fuss", + "##tao", + "##wash", + "brat", + "oppressive", + "reykjavik", + "spartak", + "ticking", + "##inkles", + "##kiewicz", + "adolph", + "horst", + "maui", + "protege", + "straighten", + "cpc", + "landau", + "concourse", + "clements", + "resultant", + "##ando", + "imaginative", + "joo", + "reactivated", + "##rem", + "##ffled", + "##uising", + "consultative", + "##guide", + "flop", + "kaitlyn", + "mergers", + "parenting", + "somber", + "##vron", + "supervise", + "vidhan", + "##imum", + "courtship", + "exemplified", + "harmonies", + "medallist", + "refining", + "##rrow", + "##\u043a\u0430", + "amara", + "##hum", + "780", + "goalscorer", + "sited", + "overshadowed", + "rohan", + "displeasure", + "secretive", + "multiplied", + "osman", + "##orth", + "engravings", + "padre", + "##kali", + "##veda", + "miniatures", + "mis", + "##yala", + "clap", + "pali", + "rook", + "##cana", + "1692", + "57th", + "antennae", + "astro", + "oskar", + "1628", + "bulldog", + "crotch", + "hackett", + "yucatan", + "##sure", + "amplifiers", + "brno", + "ferrara", + "migrating", + "##gree", + "thanking", + "turing", + "##eza", + "mccann", + "ting", + "andersson", + "onslaught", + "gaines", + "ganga", + "incense", + "standardization", + "##mation", + "sentai", + "scuba", + "stuffing", + "turquoise", + "waivers", + "alloys", + "##vitt", + "regaining", + "vaults", + "##clops", + "##gizing", + "digger", + "furry", + "memorabilia", + "probing", + "##iad", + "payton", + "rec", + "deutschland", + "filippo", + "opaque", + "seamen", + "zenith", + "afrikaans", + "##filtration", + "disciplined", + "inspirational", + "##merie", + "banco", + "confuse", + "grafton", + "tod", + "##dgets", + "championed", + "simi", + "anomaly", + "biplane", + "##ceptive", + "electrode", + "##para", + "1697", + "cleavage", + "crossbow", + "swirl", + "informant", + "##lars", + "##osta", + "afi", + "bonfire", + "spec", + "##oux", + "lakeside", + "slump", + "##culus", + "##lais", + "##qvist", + "##rrigan", + "1016", + "facades", + "borg", + "inwardly", + "cervical", + "xl", + "pointedly", + "050", + "stabilization", + "##odon", + "chests", + "1699", + "hacked", + "ctv", + "orthogonal", + "suzy", + "##lastic", + "gaulle", + "jacobite", + "rearview", + "##cam", + "##erted", + "ashby", + "##drik", + "##igate", + "##mise", + "##zbek", + "affectionately", + "canine", + "disperse", + "latham", + "##istles", + "##ivar", + "spielberg", + "##orin", + "##idium", + "ezekiel", + "cid", + "##sg", + "durga", + "middletown", + "##cina", + "customized", + "frontiers", + "harden", + "##etano", + "##zzy", + "1604", + "bolsheviks", + "##66", + "coloration", + "yoko", + "##bedo", + "briefs", + "slabs", + "debra", + "liquidation", + "plumage", + "##oin", + "blossoms", + "dementia", + "subsidy", + "1611", + "proctor", + "relational", + "jerseys", + "parochial", + "ter", + "##ici", + "esa", + "peshawar", + "cavalier", + "loren", + "cpi", + "idiots", + "shamrock", + "1646", + "dutton", + "malabar", + "mustache", + "##endez", + "##ocytes", + "referencing", + "terminates", + "marche", + "yarmouth", + "##sop", + "acton", + "mated", + "seton", + "subtly", + "baptised", + "beige", + "extremes", + "jolted", + "kristina", + "telecast", + "##actic", + "safeguard", + "waldo", + "##baldi", + "##bular", + "endeavors", + "sloppy", + "subterranean", + "##ensburg", + "##itung", + "delicately", + "pigment", + "tq", + "##scu", + "1626", + "##ound", + "collisions", + "coveted", + "herds", + "##personal", + "##meister", + "##nberger", + "chopra", + "##ricting", + "abnormalities", + "defective", + "galician", + "lucie", + "##dilly", + "alligator", + "likened", + "##genase", + "burundi", + "clears", + "complexion", + "derelict", + "deafening", + "diablo", + "fingered", + "champaign", + "dogg", + "enlist", + "isotope", + "labeling", + "mrna", + "##erre", + "brilliance", + "marvelous", + "##ayo", + "1652", + "crawley", + "ether", + "footed", + "dwellers", + "deserts", + "hamish", + "rubs", + "warlock", + "skimmed", + "##lizer", + "870", + "buick", + "embark", + "heraldic", + "irregularities", + "##ajan", + "kiara", + "##kulam", + "##ieg", + "antigen", + "kowalski", + "##lge", + "oakley", + "visitation", + "##mbit", + "vt", + "##suit", + "1570", + "murderers", + "##miento", + "##rites", + "chimneys", + "##sling", + "condemn", + "custer", + "exchequer", + "havre", + "##ghi", + "fluctuations", + "##rations", + "dfb", + "hendricks", + "vaccines", + "##tarian", + "nietzsche", + "biking", + "juicy", + "##duced", + "brooding", + "scrolling", + "selangor", + "##ragan", + "352", + "annum", + "boomed", + "seminole", + "sugarcane", + "##dna", + "departmental", + "dismissing", + "innsbruck", + "arteries", + "ashok", + "batavia", + "daze", + "kun", + "overtook", + "##rga", + "##tlan", + "beheaded", + "gaddafi", + "holm", + "electronically", + "faulty", + "galilee", + "fractures", + "kobayashi", + "##lized", + "gunmen", + "magma", + "aramaic", + "mala", + "eastenders", + "inference", + "messengers", + "bf", + "##qu", + "407", + "bathrooms", + "##vere", + "1658", + "flashbacks", + "ideally", + "misunderstood", + "##jali", + "##weather", + "mendez", + "##grounds", + "505", + "uncanny", + "##iii", + "1709", + "friendships", + "##nbc", + "sacrament", + "accommodated", + "reiterated", + "logistical", + "pebbles", + "thumped", + "##escence", + "administering", + "decrees", + "drafts", + "##flight", + "##cased", + "##tula", + "futuristic", + "picket", + "intimidation", + "winthrop", + "##fahan", + "interfered", + "339", + "afar", + "francoise", + "morally", + "uta", + "cochin", + "croft", + "dwarfs", + "##bruck", + "##dents", + "##nami", + "biker", + "##hner", + "##meral", + "nano", + "##isen", + "##ometric", + "##pres", + "##\u0430\u043d", + "brightened", + "meek", + "parcels", + "securely", + "gunners", + "##jhl", + "##zko", + "agile", + "hysteria", + "##lten", + "##rcus", + "bukit", + "champs", + "chevy", + "cuckoo", + "leith", + "sadler", + "theologians", + "welded", + "##section", + "1663", + "jj", + "plurality", + "xander", + "##rooms", + "##formed", + "shredded", + "temps", + "intimately", + "pau", + "tormented", + "##lok", + "##stellar", + "1618", + "charred", + "ems", + "essen", + "##mmel", + "alarms", + "spraying", + "ascot", + "blooms", + "twinkle", + "##abia", + "##apes", + "internment", + "obsidian", + "##chaft", + "snoop", + "##dav", + "##ooping", + "malibu", + "##tension", + "quiver", + "##itia", + "hays", + "mcintosh", + "travers", + "walsall", + "##ffie", + "1623", + "beverley", + "schwarz", + "plunging", + "structurally", + "m3", + "rosenthal", + "vikram", + "##tsk", + "770", + "ghz", + "##onda", + "##tiv", + "chalmers", + "groningen", + "pew", + "reckon", + "unicef", + "##rvis", + "55th", + "##gni", + "1651", + "sulawesi", + "avila", + "cai", + "metaphysical", + "screwing", + "turbulence", + "##mberg", + "augusto", + "samba", + "56th", + "baffled", + "momentary", + "toxin", + "##urian", + "##wani", + "aachen", + "condoms", + "dali", + "steppe", + "##3d", + "##app", + "##oed", + "##year", + "adolescence", + "dauphin", + "electrically", + "inaccessible", + "microscopy", + "nikita", + "##ega", + "atv", + "##cel", + "##enter", + "##oles", + "##oteric", + "##\u044b", + "accountants", + "punishments", + "wrongly", + "bribes", + "adventurous", + "clinch", + "flinders", + "southland", + "##hem", + "##kata", + "gough", + "##ciency", + "lads", + "soared", + "##\u05d4", + "undergoes", + "deformation", + "outlawed", + "rubbish", + "##arus", + "##mussen", + "##nidae", + "##rzburg", + "arcs", + "##ingdon", + "##tituted", + "1695", + "wheelbase", + "wheeling", + "bombardier", + "campground", + "zebra", + "##lices", + "##oj", + "##bain", + "lullaby", + "##ecure", + "donetsk", + "wylie", + "grenada", + "##arding", + "##\u03b7\u03c2", + "squinting", + "eireann", + "opposes", + "##andra", + "maximal", + "runes", + "##broken", + "##cuting", + "##iface", + "##ror", + "##rosis", + "additive", + "britney", + "adultery", + "triggering", + "##drome", + "detrimental", + "aarhus", + "containment", + "jc", + "swapped", + "vichy", + "##ioms", + "madly", + "##oric", + "##rag", + "brant", + "##ckey", + "##trix", + "1560", + "1612", + "broughton", + "rustling", + "##stems", + "##uder", + "asbestos", + "mentoring", + "##nivorous", + "finley", + "leaps", + "##isan", + "apical", + "pry", + "slits", + "substitutes", + "##dict", + "intuitive", + "fantasia", + "insistent", + "unreasonable", + "##igen", + "##vna", + "domed", + "hannover", + "margot", + "ponder", + "##zziness", + "impromptu", + "jian", + "lc", + "rampage", + "stemming", + "##eft", + "andrey", + "gerais", + "whichever", + "amnesia", + "appropriated", + "anzac", + "clicks", + "modifying", + "ultimatum", + "cambrian", + "maids", + "verve", + "yellowstone", + "##mbs", + "conservatoire", + "##scribe", + "adherence", + "dinners", + "spectra", + "imperfect", + "mysteriously", + "sidekick", + "tatar", + "tuba", + "##aks", + "##ifolia", + "distrust", + "##athan", + "##zle", + "c2", + "ronin", + "zac", + "##pse", + "celaena", + "instrumentalist", + "scents", + "skopje", + "##mbling", + "comical", + "compensated", + "vidal", + "condor", + "intersect", + "jingle", + "wavelengths", + "##urrent", + "mcqueen", + "##izzly", + "carp", + "weasel", + "422", + "kanye", + "militias", + "postdoctoral", + "eugen", + "gunslinger", + "##\u025b", + "faux", + "hospice", + "##for", + "appalled", + "derivation", + "dwarves", + "##elis", + "dilapidated", + "##folk", + "astoria", + "philology", + "##lwyn", + "##otho", + "##saka", + "inducing", + "philanthropy", + "##bf", + "##itative", + "geek", + "markedly", + "sql", + "##yce", + "bessie", + "indices", + "rn", + "##flict", + "495", + "frowns", + "resolving", + "weightlifting", + "tugs", + "cleric", + "contentious", + "1653", + "mania", + "rms", + "##miya", + "##reate", + "##ruck", + "##tucket", + "bien", + "eels", + "marek", + "##ayton", + "##cence", + "discreet", + "unofficially", + "##ife", + "leaks", + "##bber", + "1705", + "332", + "dung", + "compressor", + "hillsborough", + "pandit", + "shillings", + "distal", + "##skin", + "381", + "##tat", + "##you", + "nosed", + "##nir", + "mangrove", + "undeveloped", + "##idia", + "textures", + "##inho", + "##500", + "##rise", + "ae", + "irritating", + "nay", + "amazingly", + "bancroft", + "apologetic", + "compassionate", + "kata", + "symphonies", + "##lovic", + "airspace", + "##lch", + "930", + "gifford", + "precautions", + "fulfillment", + "sevilla", + "vulgar", + "martinique", + "##urities", + "looting", + "piccolo", + "tidy", + "##dermott", + "quadrant", + "armchair", + "incomes", + "mathematicians", + "stampede", + "nilsson", + "##inking", + "##scan", + "foo", + "quarterfinal", + "##ostal", + "shang", + "shouldered", + "squirrels", + "##owe", + "344", + "vinegar", + "##bner", + "##rchy", + "##systems", + "delaying", + "##trics", + "ars", + "dwyer", + "rhapsody", + "sponsoring", + "##gration", + "bipolar", + "cinder", + "starters", + "##olio", + "##urst", + "421", + "signage", + "##nty", + "aground", + "figurative", + "mons", + "acquaintances", + "duets", + "erroneously", + "soyuz", + "elliptic", + "recreated", + "##cultural", + "##quette", + "##ssed", + "##tma", + "##zcz", + "moderator", + "scares", + "##itaire", + "##stones", + "##udence", + "juniper", + "sighting", + "##just", + "##nsen", + "britten", + "calabria", + "ry", + "bop", + "cramer", + "forsyth", + "stillness", + "##\u043b", + "airmen", + "gathers", + "unfit", + "##umber", + "##upt", + "taunting", + "##rip", + "seeker", + "streamlined", + "##bution", + "holster", + "schumann", + "tread", + "vox", + "##gano", + "##onzo", + "strive", + "dil", + "reforming", + "covent", + "newbury", + "predicting", + "##orro", + "decorate", + "tre", + "##puted", + "andover", + "ie", + "asahi", + "dept", + "dunkirk", + "gills", + "##tori", + "buren", + "huskies", + "##stis", + "##stov", + "abstracts", + "bets", + "loosen", + "##opa", + "1682", + "yearning", + "##glio", + "##sir", + "berman", + "effortlessly", + "enamel", + "napoli", + "persist", + "##peration", + "##uez", + "attache", + "elisa", + "b1", + "invitations", + "##kic", + "accelerating", + "reindeer", + "boardwalk", + "clutches", + "nelly", + "polka", + "starbucks", + "##kei", + "adamant", + "huey", + "lough", + "unbroken", + "adventurer", + "embroidery", + "inspecting", + "stanza", + "##ducted", + "naia", + "taluka", + "##pone", + "##roids", + "chases", + "deprivation", + "florian", + "##jing", + "##ppet", + "earthly", + "##lib", + "##ssee", + "colossal", + "foreigner", + "vet", + "freaks", + "patrice", + "rosewood", + "triassic", + "upstate", + "##pkins", + "dominates", + "ata", + "chants", + "ks", + "vo", + "##400", + "##bley", + "##raya", + "##rmed", + "555", + "agra", + "infiltrate", + "##ailing", + "##ilation", + "##tzer", + "##uppe", + "##werk", + "binoculars", + "enthusiast", + "fujian", + "squeak", + "##avs", + "abolitionist", + "almeida", + "boredom", + "hampstead", + "marsden", + "rations", + "##ands", + "inflated", + "334", + "bonuses", + "rosalie", + "patna", + "##rco", + "329", + "detachments", + "penitentiary", + "54th", + "flourishing", + "woolf", + "##dion", + "##etched", + "papyrus", + "##lster", + "##nsor", + "##toy", + "bobbed", + "dismounted", + "endelle", + "inhuman", + "motorola", + "tbs", + "wince", + "wreath", + "##ticus", + "hideout", + "inspections", + "sanjay", + "disgrace", + "infused", + "pudding", + "stalks", + "##urbed", + "arsenic", + "leases", + "##hyl", + "##rrard", + "collarbone", + "##waite", + "##wil", + "dowry", + "##bant", + "##edance", + "genealogical", + "nitrate", + "salamanca", + "scandals", + "thyroid", + "necessitated", + "##!", + "##\"", + "###", + "##$", + "##%", + "##&", + "##'", + "##(", + "##)", + "##*", + "##+", + "##,", + "##-", + "##.", + "##/", + "##:", + "##;", + "##<", + "##=", + "##>", + "##?", + "##@", + "##[", + "##\\", + "##]", + "##^", + "##_", + "##`", + "##{", + "##|", + "##}", + "##~", + "##\u00a1", + "##\u00a2", + "##\u00a3", + "##\u00a4", + "##\u00a5", + "##\u00a6", + "##\u00a7", + "##\u00a8", + "##\u00a9", + "##\u00aa", + "##\u00ab", + "##\u00ac", + "##\u00ae", + "##\u00b1", + "##\u00b4", + "##\u00b5", + "##\u00b6", + "##\u00b7", + "##\u00ba", + "##\u00bb", + "##\u00bc", + "##\u00be", + "##\u00bf", + "##\u00e6", + "##\u00f0", + "##\u00f7", + "##\u00fe", + "##\u0111", + "##\u0127", + "##\u014b", + "##\u0153", + "##\u0192", + "##\u0250", + "##\u0251", + "##\u0252", + "##\u0254", + "##\u0255", + "##\u0259", + "##\u0261", + "##\u0263", + "##\u0268", + "##\u026a", + "##\u026b", + "##\u026c", + "##\u026f", + "##\u0272", + "##\u0274", + "##\u0279", + "##\u027e", + "##\u0280", + "##\u0281", + "##\u0282", + "##\u0283", + "##\u0289", + "##\u028a", + "##\u028b", + "##\u028c", + "##\u028e", + "##\u0290", + "##\u0291", + "##\u0292", + "##\u0294", + "##\u02b0", + "##\u02b2", + "##\u02b3", + "##\u02b7", + "##\u02b8", + "##\u02bb", + "##\u02bc", + "##\u02be", + "##\u02bf", + "##\u02c8", + "##\u02e1", + "##\u02e2", + "##\u02e3", + "##\u02e4", + "##\u03b2", + "##\u03b3", + "##\u03b4", + "##\u03b5", + "##\u03b6", + "##\u03b8", + "##\u03ba", + "##\u03bb", + "##\u03bc", + "##\u03be", + "##\u03bf", + "##\u03c0", + "##\u03c1", + "##\u03c3", + "##\u03c4", + "##\u03c5", + "##\u03c6", + "##\u03c7", + "##\u03c8", + "##\u03c9", + "##\u0431", + "##\u0433", + "##\u0434", + "##\u0436", + "##\u0437", + "##\u043c", + "##\u043f", + "##\u0441", + "##\u0443", + "##\u0444", + "##\u0445", + "##\u0446", + "##\u0447", + "##\u0448", + "##\u0449", + "##\u044a", + "##\u044d", + "##\u044e", + "##\u0452", + "##\u0454", + "##\u0456", + "##\u0458", + "##\u0459", + "##\u045a", + "##\u045b", + "##\u04cf", + "##\u0561", + "##\u0562", + "##\u0563", + "##\u0564", + "##\u0565", + "##\u0569", + "##\u056b", + "##\u056c", + "##\u056f", + "##\u0570", + "##\u0574", + "##\u0575", + "##\u0576", + "##\u0578", + "##\u057a", + "##\u057d", + "##\u057e", + "##\u057f", + "##\u0580", + "##\u0582", + "##\u0584", + "##\u05be", + "##\u05d0", + "##\u05d1", + "##\u05d2", + "##\u05d3", + "##\u05d5", + "##\u05d6", + "##\u05d7", + "##\u05d8", + "##\u05d9", + "##\u05da", + "##\u05db", + "##\u05dc", + "##\u05dd", + "##\u05de", + "##\u05df", + "##\u05e0", + "##\u05e1", + "##\u05e2", + "##\u05e3", + "##\u05e4", + "##\u05e5", + "##\u05e6", + "##\u05e7", + "##\u05e8", + "##\u05e9", + "##\u05ea", + "##\u060c", + "##\u0621", + "##\u0628", + "##\u062a", + "##\u062b", + "##\u062c", + "##\u062d", + "##\u062e", + "##\u0630", + "##\u0632", + "##\u0633", + "##\u0634", + "##\u0635", + "##\u0636", + "##\u0637", + "##\u0638", + "##\u0639", + "##\u063a", + "##\u0640", + "##\u0641", + "##\u0642", + "##\u0643", + "##\u0648", + "##\u0649", + "##\u0679", + "##\u067e", + "##\u0686", + "##\u06a9", + "##\u06af", + "##\u06ba", + "##\u06be", + "##\u06c1", + "##\u06d2", + "##\u0905", + "##\u0906", + "##\u0909", + "##\u090f", + "##\u0915", + "##\u0916", + "##\u0917", + "##\u091a", + "##\u091c", + "##\u091f", + "##\u0921", + "##\u0923", + "##\u0924", + "##\u0925", + "##\u0926", + "##\u0927", + "##\u0928", + "##\u092a", + "##\u092c", + "##\u092d", + "##\u092e", + "##\u092f", + "##\u0930", + "##\u0932", + "##\u0935", + "##\u0936", + "##\u0937", + "##\u0938", + "##\u0939", + "##\u093e", + "##\u093f", + "##\u0940", + "##\u094b", + "##\u0964", + "##\u0965", + "##\u0982", + "##\u0985", + "##\u0986", + "##\u0987", + "##\u0989", + "##\u098f", + "##\u0993", + "##\u0995", + "##\u0996", + "##\u0997", + "##\u099a", + "##\u099b", + "##\u099c", + "##\u099f", + "##\u09a1", + "##\u09a3", + "##\u09a4", + "##\u09a5", + "##\u09a6", + "##\u09a7", + "##\u09a8", + "##\u09aa", + "##\u09ac", + "##\u09ad", + "##\u09ae", + "##\u09af", + "##\u09b0", + "##\u09b2", + "##\u09b6", + "##\u09b7", + "##\u09b8", + "##\u09b9", + "##\u09be", + "##\u09bf", + "##\u09c0", + "##\u09c7", + "##\u0b95", + "##\u0b9a", + "##\u0b9f", + "##\u0ba4", + "##\u0ba8", + "##\u0ba9", + "##\u0baa", + "##\u0bae", + "##\u0baf", + "##\u0bb0", + "##\u0bb2", + "##\u0bb3", + "##\u0bb5", + "##\u0bbe", + "##\u0bbf", + "##\u0bc1", + "##\u0bc7", + "##\u0bc8", + "##\u0ca8", + "##\u0cb0", + "##\u0cbe", + "##\u0d9a", + "##\u0dba", + "##\u0dbb", + "##\u0dbd", + "##\u0dc0", + "##\u0dcf", + "##\u0e01", + "##\u0e07", + "##\u0e15", + "##\u0e17", + "##\u0e19", + "##\u0e1e", + "##\u0e21", + "##\u0e22", + "##\u0e23", + "##\u0e25", + "##\u0e27", + "##\u0e2a", + "##\u0e2d", + "##\u0e32", + "##\u0e40", + "##\u0f0b", + "##\u0f0d", + "##\u0f42", + "##\u0f44", + "##\u0f51", + "##\u0f53", + "##\u0f54", + "##\u0f56", + "##\u0f58", + "##\u0f60", + "##\u0f62", + "##\u0f63", + "##\u0f66", + "##\u1019", + "##\u10d0", + "##\u10d1", + "##\u10d2", + "##\u10d3", + "##\u10d4", + "##\u10d5", + "##\u10d7", + "##\u10d8", + "##\u10d9", + "##\u10da", + "##\u10db", + "##\u10dc", + "##\u10dd", + "##\u10e0", + "##\u10e1", + "##\u10e2", + "##\u10e3", + "##\u1100", + "##\u1102", + "##\u1103", + "##\u1105", + "##\u1106", + "##\u1107", + "##\u1109", + "##\u110a", + "##\u110b", + "##\u110c", + "##\u110e", + "##\u110f", + "##\u1110", + "##\u1111", + "##\u1112", + "##\u1161", + "##\u1162", + "##\u1165", + "##\u1166", + "##\u1167", + "##\u1169", + "##\u116a", + "##\u116d", + "##\u116e", + "##\u116f", + "##\u1172", + "##\u1173", + "##\u1174", + "##\u1175", + "##\u11a8", + "##\u11ab", + "##\u11af", + "##\u11b7", + "##\u11b8", + "##\u11bc", + "##\u1d2c", + "##\u1d2e", + "##\u1d30", + "##\u1d35", + "##\u1d3a", + "##\u1d40", + "##\u1d43", + "##\u1d47", + "##\u1d48", + "##\u1d49", + "##\u1d4d", + "##\u1d4f", + "##\u1d50", + "##\u1d52", + "##\u1d56", + "##\u1d57", + "##\u1d58", + "##\u1d63", + "##\u1d64", + "##\u1d65", + "##\u1d9c", + "##\u1da0", + "##\u2010", + "##\u2011", + "##\u2012", + "##\u2013", + "##\u2014", + "##\u2015", + "##\u2016", + "##\u2018", + "##\u2019", + "##\u201a", + "##\u201c", + "##\u201d", + "##\u201e", + "##\u2020", + "##\u2021", + "##\u2022", + "##\u2026", + "##\u2030", + "##\u2032", + "##\u2033", + "##\u203a", + "##\u203f", + "##\u2044", + "##\u2070", + "##\u2071", + "##\u2074", + "##\u2075", + "##\u2076", + "##\u2077", + "##\u2078", + "##\u2079", + "##\u207b", + "##\u207f", + "##\u2085", + "##\u2086", + "##\u2087", + "##\u2088", + "##\u2089", + "##\u208a", + "##\u208d", + "##\u208e", + "##\u2090", + "##\u2091", + "##\u2092", + "##\u2093", + "##\u2095", + "##\u2096", + "##\u2097", + "##\u2098", + "##\u209a", + "##\u209b", + "##\u209c", + "##\u20a4", + "##\u20a9", + "##\u20ac", + "##\u20b1", + "##\u20b9", + "##\u2113", + "##\u2116", + "##\u211d", + "##\u2122", + "##\u2153", + "##\u2154", + "##\u2190", + "##\u2191", + "##\u2192", + "##\u2193", + "##\u2194", + "##\u21a6", + "##\u21c4", + "##\u21cc", + "##\u21d2", + "##\u2202", + "##\u2205", + "##\u2206", + "##\u2207", + "##\u2208", + "##\u2217", + "##\u2218", + "##\u221a", + "##\u221e", + "##\u2227", + "##\u2228", + "##\u2229", + "##\u222a", + "##\u2248", + "##\u2261", + "##\u2264", + "##\u2265", + "##\u2282", + "##\u2286", + "##\u2295", + "##\u2297", + "##\u22c5", + "##\u2500", + "##\u2502", + "##\u25a0", + "##\u25aa", + "##\u25cf", + "##\u2605", + "##\u2606", + "##\u2609", + "##\u2660", + "##\u2663", + "##\u2665", + "##\u2666", + "##\u266f", + "##\u27e8", + "##\u27e9", + "##\u2c7c", + "##\u2ea9", + "##\u2ebc", + "##\u2f65", + "##\u3001", + "##\u3002", + "##\u3008", + "##\u3009", + "##\u300a", + "##\u300b", + "##\u300c", + "##\u300d", + "##\u300e", + "##\u300f", + "##\u301c", + "##\u3042", + "##\u3044", + "##\u3046", + "##\u3048", + "##\u304a", + "##\u304b", + "##\u304d", + "##\u304f", + "##\u3051", + "##\u3053", + "##\u3055", + "##\u3057", + "##\u3059", + "##\u305b", + "##\u305d", + "##\u305f", + "##\u3061", + "##\u3063", + "##\u3064", + "##\u3066", + "##\u3068", + "##\u306a", + "##\u306b", + "##\u306c", + "##\u306d", + "##\u306e", + "##\u306f", + "##\u3072", + "##\u3075", + "##\u3078", + "##\u307b", + "##\u307e", + "##\u307f", + "##\u3080", + "##\u3081", + "##\u3082", + "##\u3084", + "##\u3086", + "##\u3088", + "##\u3089", + "##\u308a", + "##\u308b", + "##\u308c", + "##\u308d", + "##\u3092", + "##\u3093", + "##\u30a1", + "##\u30a2", + "##\u30a3", + "##\u30a4", + "##\u30a6", + "##\u30a7", + "##\u30a8", + "##\u30aa", + "##\u30ab", + "##\u30ad", + "##\u30af", + "##\u30b1", + "##\u30b3", + "##\u30b5", + "##\u30b7", + "##\u30b9", + "##\u30bb", + "##\u30bf", + "##\u30c1", + "##\u30c3", + "##\u30c4", + "##\u30c6", + "##\u30c8", + "##\u30ca", + "##\u30cb", + "##\u30ce", + "##\u30cf", + "##\u30d2", + "##\u30d5", + "##\u30d8", + "##\u30db", + "##\u30de", + "##\u30df", + "##\u30e0", + "##\u30e1", + "##\u30e2", + "##\u30e3", + "##\u30e5", + "##\u30e7", + "##\u30e9", + "##\u30ea", + "##\u30eb", + "##\u30ec", + "##\u30ed", + "##\u30ef", + "##\u30f3", + "##\u30fb", + "##\u30fc", + "##\u4e00", + "##\u4e09", + "##\u4e0a", + "##\u4e0b", + "##\u4e0d", + "##\u4e16", + "##\u4e2d", + "##\u4e3b", + "##\u4e45", + "##\u4e4b", + "##\u4e5f", + "##\u4e8b", + "##\u4e8c", + "##\u4e94", + "##\u4e95", + "##\u4eac", + "##\u4eba", + "##\u4ebb", + "##\u4ec1", + "##\u4ecb", + "##\u4ee3", + "##\u4eee", + "##\u4f0a", + "##\u4f1a", + "##\u4f50", + "##\u4f8d", + "##\u4fdd", + "##\u4fe1", + "##\u5065", + "##\u5143", + "##\u5149", + "##\u516b", + "##\u516c", + "##\u5185", + "##\u51fa", + "##\u5206", + "##\u524d", + "##\u5289", + "##\u529b", + "##\u52a0", + "##\u52dd", + "##\u5317", + "##\u533a", + "##\u5341", + "##\u5343", + "##\u5357", + "##\u535a", + "##\u539f", + "##\u53e3", + "##\u53e4", + "##\u53f2", + "##\u53f8", + "##\u5408", + "##\u5409", + "##\u540c", + "##\u540d", + "##\u548c", + "##\u56d7", + "##\u56db", + "##\u56fd", + "##\u570b", + "##\u571f", + "##\u5730", + "##\u5742", + "##\u57ce", + "##\u5802", + "##\u5834", + "##\u58eb", + "##\u590f", + "##\u5916", + "##\u5927", + "##\u5929", + "##\u592a", + "##\u592b", + "##\u5948", + "##\u5973", + "##\u5b50", + "##\u5b66", + "##\u5b80", + "##\u5b87", + "##\u5b89", + "##\u5b97", + "##\u5b9a", + "##\u5ba3", + "##\u5bae", + "##\u5bb6", + "##\u5bbf", + "##\u5bfa", + "##\u5c07", + "##\u5c0f", + "##\u5c1a", + "##\u5c71", + "##\u5ca1", + "##\u5cf6", + "##\u5d0e", + "##\u5ddd", + "##\u5dde", + "##\u5dff", + "##\u5e1d", + "##\u5e73", + "##\u5e74", + "##\u5e78", + "##\u5e7f", + "##\u5f18", + "##\u5f35", + "##\u5f73", + "##\u5f8c", + "##\u5fa1", + "##\u5fb7", + "##\u5fc3", + "##\u5fc4", + "##\u5fd7", + "##\u5fe0", + "##\u611b", + "##\u6210", + "##\u6211", + "##\u6226", + "##\u6238", + "##\u624b", + "##\u624c", + "##\u653f", + "##\u6587", + "##\u65b0", + "##\u65b9", + "##\u65e5", + "##\u660e", + "##\u661f", + "##\u6625", + "##\u662d", + "##\u667a", + "##\u66f2", + "##\u66f8", + "##\u6708", + "##\u6709", + "##\u671d", + "##\u6728", + "##\u672c", + "##\u674e", + "##\u6751", + "##\u6771", + "##\u677e", + "##\u6797", + "##\u68ee", + "##\u694a", + "##\u6a39", + "##\u6a4b", + "##\u6b4c", + "##\u6b62", + "##\u6b63", + "##\u6b66", + "##\u6bd4", + "##\u6c0f", + "##\u6c11", + "##\u6c34", + "##\u6c35", + "##\u6c37", + "##\u6c38", + "##\u6c5f", + "##\u6ca2", + "##\u6cb3", + "##\u6cbb", + "##\u6cd5", + "##\u6d77", + "##\u6e05", + "##\u6f22", + "##\u702c", + "##\u706b", + "##\u7248", + "##\u72ac", + "##\u738b", + "##\u751f", + "##\u7530", + "##\u7537", + "##\u7592", + "##\u767a", + "##\u767d", + "##\u7684", + "##\u7687", + "##\u76ee", + "##\u76f8", + "##\u7701", + "##\u771f", + "##\u77f3", + "##\u793a", + "##\u793e", + "##\u795e", + "##\u798f", + "##\u79be", + "##\u79c0", + "##\u79cb", + "##\u7a7a", + "##\u7acb", + "##\u7ae0", + "##\u7af9", + "##\u7cf9", + "##\u7f8e", + "##\u7fa9", + "##\u8033", + "##\u826f", + "##\u8279", + "##\u82b1", + "##\u82f1", + "##\u83ef", + "##\u8449", + "##\u85e4", + "##\u884c", + "##\u8857", + "##\u897f", + "##\u898b", + "##\u8a01", + "##\u8a9e", + "##\u8c37", + "##\u8c9d", + "##\u8cb4", + "##\u8eca", + "##\u8ecd", + "##\u8fb6", + "##\u9053", + "##\u90ce", + "##\u90e1", + "##\u90e8", + "##\u90fd", + "##\u91cc", + "##\u91ce", + "##\u91d1", + "##\u9234", + "##\u9547", + "##\u9577", + "##\u9580", + "##\u9593", + "##\u961d", + "##\u963f", + "##\u9673", + "##\u967d", + "##\u96c4", + "##\u9752", + "##\u9762", + "##\u98a8", + "##\u98df", + "##\u9999", + "##\u99ac", + "##\u9ad8", + "##\u9f8d", + "##\u9fb8", + "##\ufb01", + "##\ufb02", + "##\uff01", + "##\uff08", + "##\uff09", + "##\uff0c", + "##\uff0d", + "##\uff0e", + "##\uff0f", + "##\uff1a", + "##\uff1f", + "##\uff5e" + ] +} diff --git a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts index 4d622d08d6b41..ea8b375df7fc9 100644 --- a/x-pack/test/security_solution_api_integration/config/ess/config.base.ts +++ b/x-pack/test/security_solution_api_integration/config/ess/config.base.ts @@ -79,6 +79,7 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s '--xpack.ruleRegistry.unsafe.indexUpgrade.enabled=true', '--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true', `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'alertSuppressionForNewTermsRuleEnabled', 'previewTelemetryUrlEnabled', 'riskScoringPersistence', 'riskScoringRoutesEnabled', diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts index 137ee1f67b9b3..9c7a64ed00c89 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/configs/serverless.config.ts @@ -17,5 +17,8 @@ export default createTestConfig({ 'testing_ignored.constant', '/testing_regex*/', ])}`, // See tests within the file "ignore_fields.ts" which use these values in "alertIgnoreFields" + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'alertSuppressionForNewTermsRuleEnabled', + ])}`, ], }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts index 19b3943fd33b2..2b7912cc611af 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/eql.ts @@ -33,7 +33,7 @@ import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solut import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { getEqlRuleForAlertTesting, - getOpenAlerts, + getAlerts, getPreviewAlerts, previewRule, dataGeneratorFactory, @@ -98,7 +98,7 @@ export default ({ getService }: FtrProviderContext) => { query: specificQueryForTests, }; const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); expect(alerts.hits.hits.length).eql(1); const fullAlert = alerts.hits.hits[0]._source; if (!fullAlert) { diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts index c3267aadb234b..7fd34aff67690 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/esql.ts @@ -17,7 +17,7 @@ import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/ import { getPreviewAlerts, previewRule, - getOpenAlerts, + getAlerts, dataGeneratorFactory, previewRuleWithExceptionEntries, removeRandomValuedPropertiesFromAlert, @@ -83,7 +83,7 @@ export default ({ getService }: FtrProviderContext) => { }); const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); expect(alerts.hits.hits.length).toBe(1); expect(removeRandomValuedPropertiesFromAlert(alerts.hits.hits[0]._source)).toEqual({ @@ -786,7 +786,7 @@ export default ({ getService }: FtrProviderContext) => { const createdRule = await createRule(supertest, log, rule); // first rule run should generate 100 alerts from first 3 batches of index documents - const alertsResponseFromFirstRuleExecution = await getOpenAlerts( + const alertsResponseFromFirstRuleExecution = await getAlerts( supertest, log, es, @@ -811,7 +811,7 @@ export default ({ getService }: FtrProviderContext) => { enabled: true, }); - const alertsResponse = await getOpenAlerts( + const alertsResponse = await getAlerts( supertest, log, es, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/index.ts index 31c9b648e9cd3..7190ad93eeacb 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/index.ts @@ -13,6 +13,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./esql')); loadTestFile(require.resolve('./machine_learning')); loadTestFile(require.resolve('./new_terms')); + loadTestFile(require.resolve('./new_terms_alert_suppression')); loadTestFile(require.resolve('./saved_query')); loadTestFile(require.resolve('./threat_match')); loadTestFile(require.resolve('./threat_match_alert_suppression')); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts index c02f92f1703db..157228fe53c10 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/machine_learning.ts @@ -37,7 +37,7 @@ import { import { executeSetupModuleRequest, forceStartDatafeeds, - getOpenAlerts, + getAlerts, getPreviewAlerts, previewRule, previewRuleWithExceptionEntries, @@ -96,7 +96,7 @@ export default ({ getService }: FtrProviderContext) => { // First test creates a real rule - remaining tests use preview API it('should create 1 alert from ML rule when record meets anomaly_threshold', async () => { const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); expect(alerts.hits.hits.length).toBe(1); const alert = alerts.hits.hits[0]; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts index 871bcbcfd01af..f87595c0d7bb4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms.ts @@ -15,7 +15,7 @@ import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/ import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { - getOpenAlerts, + getAlerts, getPreviewAlerts, previewRule, dataGeneratorFactory, @@ -108,7 +108,7 @@ export default ({ getService }: FtrProviderContext) => { }; const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); expect(alerts.hits.hits.length).eql(1); expect(removeRandomValuedPropertiesFromAlert(alerts.hits.hits[0]._source)).eql({ diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts new file mode 100644 index 0000000000000..654781617f097 --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/new_terms_alert_suppression.ts @@ -0,0 +1,2230 @@ +/* + * 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 { v4 as uuidv4 } from 'uuid'; +import expect from 'expect'; + +import { + ALERT_SUPPRESSION_START, + ALERT_SUPPRESSION_END, + ALERT_SUPPRESSION_DOCS_COUNT, + ALERT_SUPPRESSION_TERMS, + ALERT_LAST_DETECTED, + TIMESTAMP, + ALERT_START, +} from '@kbn/rule-data-utils'; +import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; +import { getSuppressionMaxSignalsWarning as getSuppressionMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; +import { getCreateNewTermsRulesSchemaMock } from '@kbn/security-solution-plugin/common/api/detection_engine/model/rule_schema/mocks'; +import { NewTermsRuleCreateProps } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +import { DETECTION_ENGINE_SIGNALS_STATUS_URL as DETECTION_ENGINE_ALERTS_STATUS_URL } from '@kbn/security-solution-plugin/common/constants'; + +import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; + +import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; +import { createRule } from '../../../../../../../common/utils/security_solution'; +import { + getOpenAlerts, + getPreviewAlerts, + previewRule, + previewRuleWithExceptionEntries, + patchRule, + setAlertStatus, + dataGeneratorFactory, +} from '../../../../utils'; +import { FtrProviderContext } from '../../../../../../ftr_provider_context'; +import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/utils'; +import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const log = getService('log'); + + const { indexListOfDocuments, indexGeneratedDocuments } = dataGeneratorFactory({ + es, + index: 'ecs_compliant', + log, + }); + + const historicalWindowStart = '2019-10-13T05:00:04.000Z'; + + describe('@ess @serverless New terms type rules, alert suppression', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/security_solution/ecs_compliant'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/ecs_compliant'); + }); + + it('should suppress an alert during real rule executions', async () => { + const id = uuidv4(); + const firstTimestamp = new Date().toISOString(); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + alert_suppression: { + group_by: ['host.name'], + duration: { + value: 300, + unit: 'm', + }, + missing_fields_strategy: 'suppress', + }, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + from: 'now-35m', + interval: '30m', + query: `id: "${id}"`, + }; + + const historicalDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.1' }, + id, + '@timestamp': historicalWindowStart, + }, + { + host: { name: 'host-0', ip: '127.0.0.2' }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + + const firstExecutionDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.3' }, + id, + '@timestamp': firstTimestamp, + }, + { + host: { name: 'host-0', ip: '127.0.0.3' }, + id, + '@timestamp': firstTimestamp, + }, + { + host: { name: 'host-0', ip: '127.0.0.4' }, + id, + '@timestamp': firstTimestamp, + }, + ]; + + await indexListOfDocuments([...historicalDocuments, ...firstExecutionDocuments]); + + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenAlerts(supertest, log, es, createdRule); + expect(alerts.hits.hits.length).toEqual(1); + + expect(alerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-0'], + }, + ], + // suppression boundaries equal to original event time, since no alert been suppressed + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: firstTimestamp, + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }) + ); + + const secondTimestamp = new Date().toISOString(); + const secondExecutionDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.5' }, + id, + '@timestamp': secondTimestamp, + }, + ]; + // Add a new document, then disable and re-enable to trigger another rule run. The second doc should + // trigger an update to the existing alert without changing the timestamp + await indexListOfDocuments(secondExecutionDocuments); + + await patchRule(supertest, log, { id: createdRule.id, enabled: false }); + await patchRule(supertest, log, { id: createdRule.id, enabled: true }); + const afterTimestamp = new Date(); + const secondAlerts = await getOpenAlerts( + supertest, + log, + es, + createdRule, + RuleExecutionStatusEnum.succeeded, + undefined, + afterTimestamp + ); + expect(secondAlerts.hits.hits.length).toEqual(1); + expect(secondAlerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-0'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, // timestamp is the same + [ALERT_SUPPRESSION_START]: firstTimestamp, // suppression start is the same + [ALERT_SUPPRESSION_END]: secondTimestamp, // suppression end is updated + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, // 2 alerts from second rule run, that's why 2 suppressed + }) + ); + }); + + it('should NOT suppress and update an alert if the alert is closed', async () => { + const id = uuidv4(); + const firstTimestamp = new Date().toISOString(); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock(id, true), + new_terms_fields: ['host.ip'], + alert_suppression: { + group_by: ['host.name'], + duration: { + value: 300, + unit: 'm', + }, + missing_fields_strategy: 'suppress', + }, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + from: 'now-35m', + interval: '30m', + query: `id: "${id}"`, + }; + + const historicalDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.1' }, + id, + '@timestamp': historicalWindowStart, + }, + { + host: { name: 'host-0', ip: '127.0.0.2' }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + + const firstExecutionDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.3' }, + id, + '@timestamp': firstTimestamp, + }, + ]; + + await indexListOfDocuments([...historicalDocuments, ...firstExecutionDocuments]); + + const createdRule = await createRule(supertest, log, rule); + const alerts = await getOpenAlerts(supertest, log, es, createdRule); + expect(alerts.hits.hits).toHaveLength(1); + // Close the alert. Subsequent rule executions should ignore this closed alert + // for suppression purposes. + const alertIds = alerts.hits.hits.map((alert) => alert._id); + await supertest + .post(DETECTION_ENGINE_ALERTS_STATUS_URL) + .set('kbn-xsrf', 'true') + .send(setAlertStatus({ alertIds, status: 'closed' })) + .expect(200); + + const secondTimestamp = new Date().toISOString(); + const secondExecutionDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.5' }, + id, + '@timestamp': secondTimestamp, + }, + ]; + // Add new documents, then disable and re-enable to trigger another rule run. The second doc should + // trigger a new alert since the first one is now closed. + await indexListOfDocuments(secondExecutionDocuments); + + await patchRule(supertest, log, { id: createdRule.id, enabled: false }); + await patchRule(supertest, log, { id: createdRule.id, enabled: true }); + const afterTimestamp = new Date(); + const secondAlerts = await getOpenAlerts( + supertest, + log, + es, + createdRule, + RuleExecutionStatusEnum.succeeded, + undefined, + afterTimestamp + ); + expect(secondAlerts.hits.hits.length).toEqual(1); + expect(alerts.hits.hits[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-0'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + }); + + it('should NOT suppress alerts when suppression period is less than rule interval', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T05:45:00.000Z'; + const secondTimestamp = '2020-10-28T06:15:00.000Z'; + + const historicalDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.1' }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.3' }, + id, + '@timestamp': firstTimestamp, + }, + ]; + const secondExecutionDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.5' }, + id, + '@timestamp': secondTimestamp, + }, + ]; + await indexListOfDocuments([ + ...historicalDocuments, + ...firstExecutionDocuments, + ...secondExecutionDocuments, + ]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + alert_suppression: { + group_by: ['host.name'], + duration: { + value: 20, + unit: 'm', + }, + missing_fields_strategy: 'suppress', + }, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + from: 'now-35m', + interval: '30m', + query: `id: "${id}"`, + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 2, + }); + + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toBe(2); + expect(previewAlerts[0]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-0'], + }, + ], + [TIMESTAMP]: '2020-10-28T06:00:00.000Z', + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: firstTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + + expect(previewAlerts[1]._source).toEqual( + expect.objectContaining({ + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-0'], + }, + ], + [TIMESTAMP]: '2020-10-28T06:30:00.000Z', + [ALERT_SUPPRESSION_START]: secondTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }) + ); + }); + + it('should suppress alerts in the time window that covers 3 rule executions', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T05:45:00.000Z'; + const secondTimestamp = '2020-10-28T06:15:00.000Z'; + const thirdTimestamp = '2020-10-28T06:45:00.000Z'; + const historicalDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.1' }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.3' }, + id, + '@timestamp': firstTimestamp, + }, + { + host: { name: 'host-0', ip: '127.0.0.4' }, + id, + '@timestamp': firstTimestamp, + }, + ]; + const secondExecutionDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.5' }, + id, + '@timestamp': secondTimestamp, + }, + ]; + const thirdExecutionDocuments = [ + { + host: { name: 'host-0', ip: '127.0.0.7' }, + id, + '@timestamp': thirdTimestamp, + }, + ]; + + await indexListOfDocuments([ + ...historicalDocuments, + ...firstExecutionDocuments, + ...secondExecutionDocuments, + ...thirdExecutionDocuments, + ]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + alert_suppression: { + group_by: ['host.name'], + duration: { + value: 2, + unit: 'h', + }, + missing_fields_strategy: 'suppress', + }, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + from: 'now-35m', + interval: '30m', + query: `id: "${id}"`, + }; + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + invocationCount: 3, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-0'], + }, + ], + [TIMESTAMP]: '2020-10-28T06:00:00.000Z', + [ALERT_LAST_DETECTED]: '2020-10-28T07:00:00.000Z', // Note: ALERT_LAST_DETECTED gets updated, timestamp does not + [ALERT_START]: '2020-10-28T06:00:00.000Z', + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: thirdTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 3, // in total 3 alert got suppressed: 1 from the first run, 1 from the second, 1 from the third + }); + }); + + it('should suppress the correct alerts based on multi values group_by', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T05:45:00.000Z'; + const secondTimestamp = '2020-10-28T06:15:00.000Z'; + + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + 'agent.version': 1, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.3' }, + id, + '@timestamp': firstTimestamp, + 'agent.version': 1, + }, + { + host: { name: 'host-a', ip: '127.0.0.4' }, + id, + '@timestamp': firstTimestamp, + 'agent.version': 2, + }, + { + host: { name: 'host-b', ip: '127.0.0.1' }, + id, + '@timestamp': firstTimestamp, + 'agent.version': 2, + }, + ]; + const secondExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.10' }, + id, + '@timestamp': secondTimestamp, + 'agent.version': 1, + }, + ]; + + await indexListOfDocuments([ + ...historicalDocuments, + ...firstExecutionDocuments, + ...secondExecutionDocuments, + ]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + alert_suppression: { + group_by: ['host.name', 'agent.version'], + duration: { + value: 2, + unit: 'h', + }, + missing_fields_strategy: 'suppress', + }, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + from: 'now-35m', + interval: '30m', + query: `id: "${id}"`, + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 2, + }); + + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['host.name', 'agent.version', ALERT_ORIGINAL_TIME], + }); + // 3 alerts should be generated: + // 1. for pair 'host-a', 1 - suppressed + // 2. for pair 'host-a', 2 - not suppressed + expect(previewAlerts.length).toEqual(2); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + { + field: 'agent.version', + value: ['1'], + }, + ], + [TIMESTAMP]: '2020-10-28T06:00:00.000Z', + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + expect(previewAlerts[1]._source).toEqual({ + ...previewAlerts[1]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + { + field: 'agent.version', + value: ['2'], + }, + ], + [TIMESTAMP]: '2020-10-28T06:00:00.000Z', + [ALERT_LAST_DETECTED]: '2020-10-28T06:00:00.000Z', + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: firstTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, // no suppressed alerts + }); + }); + + it('should correctly suppress when using a timestamp override', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T05:45:00.000Z'; + const secondTimestamp = '2020-10-28T06:10:00.000Z'; + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const docWithoutOverride = { + id, + '@timestamp': firstTimestamp, + host: { name: 'host-a', ip: '127.0.0.2' }, + event: { + ingested: firstTimestamp, + }, + }; + const docWithOverride = { + ...docWithoutOverride, + host: { name: 'host-a', ip: '127.0.0.3' }, + // This simulates a very late arriving doc + '@timestamp': '2020-10-28T03:00:00.000Z', + event: { + ingested: secondTimestamp, + }, + }; + + await indexListOfDocuments([...historicalDocuments, docWithoutOverride, docWithOverride]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + alert_suppression: { + group_by: ['host.name'], + duration: { + value: 2, + unit: 'h', + }, + missing_fields_strategy: 'suppress', + }, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + from: 'now-35m', + interval: '30m', + query: `id: "${id}"`, + timestamp_override: 'event.ingested', + }; + + // 1 alert should be suppressed, based on event.ingested value of a document + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 2, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['host.name', ALERT_ORIGINAL_TIME], + }); + + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + }); + + it('should deduplicate multiple alerts while suppressing new ones', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T05:45:00.000Z'; + const secondTimestamp = '2020-10-28T06:10:00.000Z'; + + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + 'agent.version': 1, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.3' }, + id, + '@timestamp': firstTimestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.4' }, + id, + '@timestamp': firstTimestamp, + }, + ]; + const secondExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.5' }, + id, + '@timestamp': secondTimestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.6' }, + id, + '@timestamp': secondTimestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.7' }, + id, + '@timestamp': secondTimestamp, + }, + ]; + + await indexListOfDocuments([ + ...historicalDocuments, + ...firstExecutionDocuments, + ...secondExecutionDocuments, + ]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['host.name'], + duration: { + value: 300, + unit: 'm', + }, + missing_fields_strategy: 'suppress', + }, + // large look-back time covers all docs + from: 'now-1h', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 2, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['host.name', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 4, + }); + }); + + it('should suppress alerts with missing fields', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T05:45:00.000Z'; + const secondTimestamp = '2020-10-28T06:10:00.000Z'; + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + 'agent.version': 1, + id, + '@timestamp': historicalWindowStart, + }, + ]; + + const firstExecutionDocuments = [ + { + id, + '@timestamp': firstTimestamp, + host: { name: 'host-a', ip: '127.0.0.3' }, + }, + { + id, + '@timestamp': firstTimestamp, + host: { name: 'host-a', ip: '127.0.0.4' }, + }, + { + id, + '@timestamp': firstTimestamp, + host: { ip: '127.0.0.5' }, // doc 1 with missing host.name field + }, + { + id, + '@timestamp': firstTimestamp, + host: { ip: '127.0.0.6' }, // doc 2 with missing host.name field + }, + ]; + const secondExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.10' }, + id, + '@timestamp': secondTimestamp, + }, + { + host: { ip: '127.0.0.11' }, // doc 3 with missing host.name field + id, + '@timestamp': secondTimestamp, + }, + ]; + + await indexListOfDocuments([ + ...historicalDocuments, + ...firstExecutionDocuments, + ...secondExecutionDocuments, + ]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['host.name'], + duration: { + value: 300, + unit: 'm', + }, + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 2, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['host.name', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(2); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, + }); + + expect(previewAlerts[1]._source).toEqual({ + ...previewAlerts[1]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: null, + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, + }); + }); + + it('should not suppress alerts with missing fields if configured so', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T05:45:00.000Z'; + const secondTimestamp = '2020-10-28T06:10:00.000Z'; + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + 'agent.version': 1, + id, + '@timestamp': historicalWindowStart, + }, + ]; + + const firstExecutionDocuments = [ + { + id, + '@timestamp': firstTimestamp, + host: { name: 'host-a', ip: '127.0.0.3' }, + }, + { + id, + '@timestamp': firstTimestamp, + host: { name: 'host-a', ip: '127.0.0.4' }, + }, + { + id, + '@timestamp': firstTimestamp, + host: { ip: '127.0.0.5' }, // doc 1 with missing host.name field + }, + { + id, + '@timestamp': firstTimestamp, + host: { ip: '127.0.0.6' }, // doc 2 with missing host.name field + }, + ]; + const secondExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.10' }, + id, + '@timestamp': secondTimestamp, + }, + { + host: { ip: '127.0.0.11' }, // doc 3 with missing host.name field + id, + '@timestamp': secondTimestamp, + }, + ]; + + await indexListOfDocuments([ + ...historicalDocuments, + ...firstExecutionDocuments, + ...secondExecutionDocuments, + ]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['host.name'], + duration: { + value: 300, + unit: 'm', + }, + missing_fields_strategy: 'doNotSuppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 2, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['host.name', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(4); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, + }); + + // rest of alerts are not suppressed and do not have suppress properties + previewAlerts.slice(1).forEach((previewAlert) => { + const source = previewAlert._source; + expect(source).toHaveProperty('id', id); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_DOCS_COUNT); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_END); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_TERMS); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_DOCS_COUNT); + }); + }); + + it('should suppress alerts for multiple new terms', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T05:45:00.000Z'; + const secondTimestamp = '2020-10-28T06:10:00.000Z'; + + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + id, + '@timestamp': historicalWindowStart, + }, + { + host: { name: 'host-a', ip: '127.0.0.2' }, + id, + '@timestamp': historicalWindowStart, + }, + { + host: { name: 'host-b', ip: '127.0.0.3' }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.11' }, // new entrance of host.ip + id, + '@timestamp': firstTimestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.2' }, + id, + '@timestamp': firstTimestamp, + }, + { + host: { name: 'host-b', ip: '127.0.0.1' }, // new combination of existing values + id, + '@timestamp': firstTimestamp, + }, + ]; + const secondExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.20' }, // new entrance of host.ip + id, + '@timestamp': secondTimestamp, + }, + { + host: { name: 'host-c', ip: '127.0.0.1' }, // new entrance of host.name + id, + '@timestamp': secondTimestamp, + }, + ]; + + await indexListOfDocuments([ + ...historicalDocuments, + ...firstExecutionDocuments, + ...secondExecutionDocuments, + ]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name', 'host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['host.name'], + duration: { + value: 300, + unit: 'm', + }, + missing_fields_strategy: 'suppress', + }, + // large look-back time covers all docs + from: 'now-1h', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 2, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['host.name', 'host.ip', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(3); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + 'kibana.alert.new_terms': ['host-a', '127.0.0.11'], + }); + expect(previewAlerts[1]._source).toEqual({ + ...previewAlerts[1]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-b'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: firstTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + 'kibana.alert.new_terms': ['host-b', '127.0.0.1'], + }); + + expect(previewAlerts[2]._source).toEqual({ + ...previewAlerts[2]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-c'], + }, + ], + [ALERT_ORIGINAL_TIME]: secondTimestamp, + [ALERT_SUPPRESSION_START]: secondTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + 'kibana.alert.new_terms': ['host-c', '127.0.0.1'], + }); + }); + + describe('rule execution only', () => { + it('should suppress alerts during rule execution only', async () => { + const id = uuidv4(); + const timestamp = '2020-10-28T06:45:00.000Z'; + const laterTimestamp = '2020-10-28T06:50:00.000Z'; + + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.3' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.4' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.5' }, + id, + '@timestamp': laterTimestamp, + }, + // does not generate alert + { + host: { name: 'host-a', ip: '127.0.0.1' }, + id, + '@timestamp': laterTimestamp, + }, + ]; + + await indexListOfDocuments([...historicalDocuments, ...firstExecutionDocuments]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['host.name'], + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + invocationCount: 1, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + ], + [TIMESTAMP]: '2020-10-28T07:00:00.000Z', + [ALERT_LAST_DETECTED]: '2020-10-28T07:00:00.000Z', + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: laterTimestamp, // suppression ends with later timestamp + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, + }); + }); + + it('should suppress alerts during rule execution only for array field', async () => { + const id = uuidv4(); + const timestamp = '2020-10-28T06:45:00.000Z'; + + const historicalDocuments = [ + { + host: { name: ['host-a', 'host-b'], ip: '127.0.0.1' }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + { + host: { name: ['host-a', 'host-b'], ip: '127.0.0.3' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: ['host-a', 'host-b'], ip: '127.0.0.4' }, + id, + '@timestamp': timestamp, + }, + ]; + + await indexListOfDocuments([...historicalDocuments, ...firstExecutionDocuments]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['host.name'], + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + invocationCount: 1, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a', 'host-b'], + }, + ], + [TIMESTAMP]: '2020-10-28T07:00:00.000Z', + [ALERT_LAST_DETECTED]: '2020-10-28T07:00:00.000Z', + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + }); + + it('should suppress alerts with missing fields during rule execution only for multiple suppress by fields', async () => { + const id = uuidv4(); + const timestamp = '2020-10-28T06:45:00.000Z'; + + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + agent: { name: 'agent-a', version: 10 }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + // no missing fields + { + host: { name: 'host-a', ip: '127.0.0.11' }, + agent: { name: 'agent-a', version: 10 }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.12' }, + agent: { name: 'agent-a', version: 10 }, + id, + '@timestamp': timestamp, + }, + // missing agent.name + { + host: { name: 'host-a', ip: '127.0.0.21' }, + agent: { version: 10 }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.22' }, + agent: { version: 10 }, + id, + '@timestamp': timestamp, + }, + // missing agent.version + { + host: { name: 'host-a', ip: '127.0.0.31' }, + agent: { name: 'agent-a' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.32' }, + agent: { name: 'agent-a' }, + id, + '@timestamp': timestamp, + }, + // missing both agent.* + { + host: { name: 'host-a', ip: '127.0.0.41' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.42' }, + id, + '@timestamp': timestamp, + }, + ]; + + await indexListOfDocuments([...historicalDocuments, ...firstExecutionDocuments]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['agent.name', 'agent.version'], + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + invocationCount: 1, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['agent.name', 'agent.version', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(4); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: ['agent-a'], + }, + { + field: 'agent.version', + value: ['10'], + }, + ], + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + + expect(previewAlerts[1]._source).toEqual({ + ...previewAlerts[1]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: ['agent-a'], + }, + { + field: 'agent.version', + value: null, + }, + ], + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + + expect(previewAlerts[2]._source).toEqual({ + ...previewAlerts[2]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: null, + }, + { + field: 'agent.version', + value: ['10'], + }, + ], + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + + expect(previewAlerts[3]._source).toEqual({ + ...previewAlerts[3]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: null, + }, + { + field: 'agent.version', + value: null, + }, + ], + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + }); + + it('should suppress alerts with missing fields during rule execution only', async () => { + const id = uuidv4(); + const timestamp = '2020-10-28T06:45:00.000Z'; + + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.3' }, + agent: { name: 'agent-1' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.4' }, + agent: { name: 'agent-1' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.5' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.6' }, + id, + '@timestamp': timestamp, + }, + ]; + + await indexListOfDocuments([...historicalDocuments, ...firstExecutionDocuments]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['agent.name'], + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + invocationCount: 1, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['agent.name', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(2); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: ['agent-1'], + }, + ], + [TIMESTAMP]: '2020-10-28T07:00:00.000Z', + [ALERT_LAST_DETECTED]: '2020-10-28T07:00:00.000Z', + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + + expect(previewAlerts[1]._source).toEqual({ + ...previewAlerts[1]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: null, + }, + ], + [TIMESTAMP]: '2020-10-28T07:00:00.000Z', + [ALERT_LAST_DETECTED]: '2020-10-28T07:00:00.000Z', + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + }); + + it('should not suppress alerts with missing fields during rule execution only if configured so', async () => { + const id = uuidv4(); + const timestamp = '2020-10-28T06:45:00.000Z'; + + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.3' }, + agent: { name: 'agent-1' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.4' }, + agent: { name: 'agent-1' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.5' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.6' }, + id, + '@timestamp': timestamp, + }, + ]; + + await indexListOfDocuments([...historicalDocuments, ...firstExecutionDocuments]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['agent.name'], + missing_fields_strategy: 'doNotSuppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + invocationCount: 1, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['agent.name', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(3); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: ['agent-1'], + }, + ], + [TIMESTAMP]: '2020-10-28T07:00:00.000Z', + [ALERT_LAST_DETECTED]: '2020-10-28T07:00:00.000Z', + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: timestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + + // rest of alerts are not suppressed and do not have suppress properties + previewAlerts.slice(1).forEach((previewAlert) => { + const source = previewAlert._source; + expect(source).toHaveProperty('id', id); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_DOCS_COUNT); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_END); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_TERMS); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_DOCS_COUNT); + }); + }); + + it('should not suppress alerts with missing fields during rule execution only if configured so for multiple suppress by fields', async () => { + const id = uuidv4(); + const timestamp = '2020-10-28T06:45:00.000Z'; + + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + agent: { name: 'agent-a', version: 10 }, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + // no missing fields + { + host: { name: 'host-a', ip: '127.0.0.11' }, + agent: { name: 'agent-a', version: 10 }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.12' }, + agent: { name: 'agent-a', version: 10 }, + id, + '@timestamp': timestamp, + }, + // missing agent.name + { + host: { name: 'host-a', ip: '127.0.0.21' }, + agent: { version: 10 }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.22' }, + agent: { version: 10 }, + id, + '@timestamp': timestamp, + }, + // missing agent.version + { + host: { name: 'host-a', ip: '127.0.0.31' }, + agent: { name: 'agent-a' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.32' }, + agent: { name: 'agent-a' }, + id, + '@timestamp': timestamp, + }, + // missing both agent.* + { + host: { name: 'host-a', ip: '127.0.0.41' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.42' }, + id, + '@timestamp': timestamp, + }, + ]; + + await indexListOfDocuments([...historicalDocuments, ...firstExecutionDocuments]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['agent.name', 'agent.version'], + missing_fields_strategy: 'doNotSuppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + invocationCount: 1, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['agent.name', 'agent.version', ALERT_ORIGINAL_TIME], + }); + // from 8 injected, only one should be suppressed + expect(previewAlerts.length).toEqual(7); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: ['agent-a'], + }, + { + field: 'agent.version', + value: ['10'], + }, + ], + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + + // rest of alerts are not suppressed and do not have suppress properties + previewAlerts.slice(1).forEach((previewAlert) => { + const source = previewAlert._source; + expect(source).toHaveProperty('id', id); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_DOCS_COUNT); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_END); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_TERMS); + expect(source).not.toHaveProperty(ALERT_SUPPRESSION_DOCS_COUNT); + }); + }); + + it('should deduplicate single alert while suppressing new ones on rule execution', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T05:45:00.000Z'; + const secondTimestamp = '2020-10-28T06:10:00.000Z'; + const historicalDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.1' }, + 'agent.version': 1, + id, + '@timestamp': historicalWindowStart, + }, + ]; + const firstExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.3' }, + id, + '@timestamp': firstTimestamp, + }, + ]; + const secondExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.5' }, + id, + '@timestamp': secondTimestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.6' }, + id, + '@timestamp': secondTimestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.7' }, + id, + '@timestamp': secondTimestamp, + }, + ]; + + await indexListOfDocuments([ + ...historicalDocuments, + ...firstExecutionDocuments, + ...secondExecutionDocuments, + ]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['host.name'], + missing_fields_strategy: 'suppress', + }, + // large look-back time covers all docs + from: 'now-1h', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 2, + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['host.name', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(2); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + ], + [ALERT_ORIGINAL_TIME]: firstTimestamp, + [ALERT_SUPPRESSION_START]: firstTimestamp, + [ALERT_SUPPRESSION_END]: firstTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 0, + }); + expect(previewAlerts[1]._source).toEqual({ + ...previewAlerts[1]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + ], + [ALERT_ORIGINAL_TIME]: secondTimestamp, + [ALERT_SUPPRESSION_START]: secondTimestamp, + [ALERT_SUPPRESSION_END]: secondTimestamp, + [ALERT_SUPPRESSION_DOCS_COUNT]: 2, + }); + }); + + it('should not suppress more than limited number 500 (max_signals * 5) for single new terms field', async () => { + const id = uuidv4(); + + await indexGeneratedDocuments({ + docsCount: 12000, + seed: (index) => ({ + id, + '@timestamp': `2020-10-28T06:50:00.${index}Z`, + host: { + name: `host-${index}`, + }, + agent: { name: 'agent-a' }, + }), + }); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['agent.name'], + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId, logs } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + invocationCount: 1, + }); + + expect(logs[0].warnings).toEqual( + expect.arrayContaining([getSuppressionMaxAlertsWarning()]) + ); + + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['agent.name', ALERT_ORIGINAL_TIME], + size: 1000, + }); + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: ['agent-a'], + }, + ], + [ALERT_SUPPRESSION_DOCS_COUNT]: 499, + }); + }); + + it('should not suppress more than limited number 500 (max_signals * 5) for multiple new terms field', async () => { + const id = uuidv4(); + + await indexGeneratedDocuments({ + docsCount: 12000, + seed: (index) => ({ + id, + '@timestamp': `2020-10-28T06:50:00.${index}Z`, + host: { + name: `host-${index}`, + }, + agent: { name: 'agent-a' }, + }), + }); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name', 'agent.name'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['agent.name'], + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId, logs } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + invocationCount: 1, + }); + + expect(logs[0].warnings).toEqual( + expect.arrayContaining([getSuppressionMaxAlertsWarning()]) + ); + + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: ['agent.name', ALERT_ORIGINAL_TIME], + size: 1000, + }); + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'agent.name', + value: ['agent-a'], + }, + ], + [ALERT_SUPPRESSION_DOCS_COUNT]: 499, + }); + }); + + it('should generate up to max_signals alerts for single new terms field', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T06:05:00.000Z'; + const secondTimestamp = '2020-10-28T06:10:00.000Z'; + + await Promise.all( + [firstTimestamp, secondTimestamp].map((t) => + indexGeneratedDocuments({ + docsCount: 20000, + seed: (index) => ({ + id, + '@timestamp': t, + host: { + name: `host-${index}`, + }, + 'agent.name': `agent-${index % 10000}`, + }), + }) + ) + ); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['agent.name'], + duration: { + value: 300, + unit: 'm', + }, + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId, logs } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 1, + }); + expect(logs[0].warnings).toEqual( + expect.arrayContaining([getSuppressionMaxAlertsWarning()]) + ); + + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + size: 1000, + sort: ['agent.name', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(100); + }); + + it('should generate up to max_signals alerts for multiple new terms fields', async () => { + const id = uuidv4(); + const firstTimestamp = '2020-10-28T06:05:00.000Z'; + const secondTimestamp = '2020-10-28T06:10:00.000Z'; + + await Promise.all( + [firstTimestamp, secondTimestamp].map((t) => + indexGeneratedDocuments({ + docsCount: 20000, + seed: (index) => ({ + id, + '@timestamp': t, + host: { + name: `host-${index}`, + }, + 'agent.name': `agent-${index % 10000}`, + }), + }) + ) + ); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name', 'agent.name'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['agent.name'], + duration: { + value: 300, + unit: 'm', + }, + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId, logs } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T06:30:00.000Z'), + invocationCount: 1, + }); + expect(logs[0].warnings).toEqual( + expect.arrayContaining([getSuppressionMaxAlertsWarning()]) + ); + + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + size: 1000, + sort: ['agent.name', ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(100); + }); + }); + + describe('with exceptions', () => { + beforeEach(async () => { + await deleteAllExceptions(supertest, log); + }); + + it('should apply exceptions when suppression configured during rule execution only', async () => { + const id = uuidv4(); + const timestamp = '2020-10-28T06:45:00.000Z'; + const laterTimestamp = '2020-10-28T06:50:00.000Z'; + + const firstExecutionDocuments = [ + { + host: { name: 'host-a', ip: '127.0.0.3' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.4' }, + id, + '@timestamp': timestamp, + }, + { + host: { name: 'host-a', ip: '127.0.0.5' }, + id, + '@timestamp': laterTimestamp, + }, + ]; + + await indexListOfDocuments([...firstExecutionDocuments]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.ip'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['host.name'], + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRuleWithExceptionEntries({ + supertest, + rule, + log, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + entries: [ + [ + { + field: 'host.ip', + operator: 'included', + type: 'match', + value: '127.0.0.4', + }, + ], + ], + }); + const previewAlerts = await getPreviewAlerts({ + es, + previewId, + sort: [ALERT_ORIGINAL_TIME], + }); + expect(previewAlerts.length).toEqual(1); + expect(previewAlerts[0]._source).toEqual({ + ...previewAlerts[0]._source, + [ALERT_SUPPRESSION_TERMS]: [ + { + field: 'host.name', + value: ['host-a'], + }, + ], + [TIMESTAMP]: '2020-10-28T07:00:00.000Z', + [ALERT_LAST_DETECTED]: '2020-10-28T07:00:00.000Z', + [ALERT_ORIGINAL_TIME]: timestamp, + [ALERT_SUPPRESSION_START]: timestamp, + [ALERT_SUPPRESSION_END]: laterTimestamp, // suppression ends with later timestamp + [ALERT_SUPPRESSION_DOCS_COUNT]: 1, + }); + }); + }); + + describe('enrichment', () => { + const config = getService('config'); + const isServerless = config.get('serverless'); + const dataPathBuilder = new EsArchivePathBuilder(isServerless); + const path = dataPathBuilder.getPath('auditbeat/hosts'); + const kibanaServer = getService('kibanaServer'); + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/entity/risks'); + await esArchiver.load(path); + await esArchiver.load('x-pack/test/functional/es_archives/asset_criticality'); + await kibanaServer.uiSettings.update({ + [ENABLE_ASSET_CRITICALITY_SETTING]: true, + }); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/entity/risks'); + await esArchiver.unload(path); + await esArchiver.unload('x-pack/test/functional/es_archives/asset_criticality'); + }); + + it('should be enriched with host risk score', async () => { + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + from: '2019-02-19T20:42:00.000Z', + history_window_start: '2019-01-19T20:42:00.000Z', + alert_suppression: { + group_by: ['host.name'], + missing_fields_strategy: 'suppress', + }, + }; + + const { previewId } = await previewRule({ supertest, rule }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + + expect(previewAlerts[0]?._source?.host?.risk?.calculated_level).toBe('Low'); + expect(previewAlerts[0]?._source?.host?.risk?.calculated_score_norm).toBe(23); + }); + + it('should be enriched alert with criticality_level', async () => { + const id = uuidv4(); + const timestamp = '2020-10-28T06:45:00.000Z'; + + const firstExecutionDocuments = [ + { + host: { name: 'zeek-newyork-sha-aa8df15', ip: '127.0.0.5' }, + user: { name: 'root' }, + id, + '@timestamp': timestamp, + }, + ]; + + await indexListOfDocuments([...firstExecutionDocuments]); + + const rule: NewTermsRuleCreateProps = { + ...getCreateNewTermsRulesSchemaMock('rule-1', true), + new_terms_fields: ['host.name'], + query: `id: "${id}"`, + index: ['ecs_compliant'], + history_window_start: historicalWindowStart, + alert_suppression: { + group_by: ['host.name'], + missing_fields_strategy: 'suppress', + }, + from: 'now-35m', + interval: '30m', + }; + + const { previewId } = await previewRule({ + supertest, + rule, + timeframeEnd: new Date('2020-10-28T07:00:00.000Z'), + }); + const previewAlerts = await getPreviewAlerts({ es, previewId }); + const fullAlert = previewAlerts[0]._source; + + expect(fullAlert?.['host.asset.criticality']).toBe('medium_impact'); + expect(fullAlert?.['user.asset.criticality']).toBe('extreme_impact'); + }); + }); + }); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/query.ts index 57f5c0c536dc1..4e09b5868204f 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/query.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/query.ts @@ -51,7 +51,7 @@ import { deleteAllExceptions } from '../../../../../lists_and_exception_lists/ut import { createExceptionList, createExceptionListItem, - getOpenAlerts, + getAlerts, getPreviewAlerts, getSimpleRule, previewRule, @@ -127,7 +127,7 @@ export default ({ getService }: FtrProviderContext) => { query: `_id:${ID}`, }; const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); expect(alerts.hits.hits.length).greaterThan(0); expect(alerts.hits.hits[0]._source?.['kibana.alert.ancestors'][0].id).eql(ID); }); @@ -811,7 +811,7 @@ export default ({ getService }: FtrProviderContext) => { }, }; const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); expect(alerts.hits.hits.length).eql(1); expect(alerts.hits.hits[0]._source).to.eql({ ...alerts.hits.hits[0]._source, @@ -841,7 +841,7 @@ export default ({ getService }: FtrProviderContext) => { await patchRule(supertest, log, { id: createdRule.id, enabled: false }); await patchRule(supertest, log, { id: createdRule.id, enabled: true }); const afterTimestamp = new Date(); - const secondAlerts = await getOpenAlerts( + const secondAlerts = await getAlerts( supertest, log, es, @@ -892,7 +892,7 @@ export default ({ getService }: FtrProviderContext) => { }, }; const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); // Close the alert. Subsequent rule executions should ignore this closed alert // for suppression purposes. @@ -917,7 +917,7 @@ export default ({ getService }: FtrProviderContext) => { await patchRule(supertest, log, { id: createdRule.id, enabled: false }); await patchRule(supertest, log, { id: createdRule.id, enabled: true }); const afterTimestamp = new Date(); - const secondAlerts = await getOpenAlerts( + const secondAlerts = await getAlerts( supertest, log, es, @@ -2357,7 +2357,7 @@ export default ({ getService }: FtrProviderContext) => { .set('elastic-api-version', '2023-10-31') .expect(200); - const alertsAfterEnable = await getOpenAlerts(supertest, log, es, ruleBody, 'succeeded'); + const alertsAfterEnable = await getAlerts(supertest, log, es, ruleBody, 'succeeded'); expect(alertsAfterEnable.hits.hits.length > 0).eql(true); }); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/saved_query.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/saved_query.ts index c44a5cb5293ae..5afbee73fa727 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/saved_query.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/saved_query.ts @@ -16,7 +16,7 @@ import { ALERT_ORIGINAL_TIME, ALERT_ORIGINAL_EVENT, } from '@kbn/security-solution-plugin/common/field_maps/field_names'; -import { getOpenAlerts } from '../../../../utils'; +import { getAlerts } from '../../../../utils'; import { createRule, deleteAllRules, @@ -63,7 +63,7 @@ export default ({ getService }: FtrProviderContext) => { saved_id: 'doesnt-exist', }; const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); const alert = alerts.hits.hits[0]._source; expect(alert).eql({ ...alert, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match.ts index 2188bf589d8c8..678750342f4ee 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match.ts @@ -38,7 +38,7 @@ import { import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solution-plugin/server/lib/detection_engine/rule_types/utils/utils'; import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; -import { previewRule, getOpenAlerts, getPreviewAlerts } from '../../../../utils'; +import { previewRule, getAlerts, getPreviewAlerts } from '../../../../utils'; import { deleteAllAlerts, deleteAllRules, @@ -175,7 +175,7 @@ export default ({ getService }: FtrProviderContext) => { const rule: ThreatMatchRuleCreateProps = createThreatMatchRule(); const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts( + const alerts = await getAlerts( supertest, log, es, @@ -356,7 +356,7 @@ export default ({ getService }: FtrProviderContext) => { }); const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts( + const alerts = await getAlerts( supertest, log, es, @@ -558,7 +558,7 @@ export default ({ getService }: FtrProviderContext) => { const createdRuleTerm = await createRule(supertest, log, termRule); const createdRuleMatch = await createRule(supertest, log, matchRule); - const alertsTerm = await getOpenAlerts( + const alertsTerm = await getAlerts( supertest, log, es, @@ -566,7 +566,7 @@ export default ({ getService }: FtrProviderContext) => { RuleExecutionStatusEnum.succeeded, 100 ); - const alertsMatch = await getOpenAlerts( + const alertsMatch = await getAlerts( supertest, log, es, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match_alert_suppression.ts index 194af2d2ea979..dec39659ae256 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threat_match_alert_suppression.ts @@ -334,8 +334,8 @@ export default ({ getService }: FtrProviderContext) => { const secondDocument = { id, '@timestamp': secondTimestamp, - agent: { - name: 'agent-1', + host: { + name: 'host-a', }, }; // Add new documents, then disable and re-enable to trigger another rule run. The second doc should diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts index 34c059d10bd87..97f1bcaf50c00 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold.ts @@ -27,7 +27,7 @@ import { getMaxSignalsWarning as getMaxAlertsWarning } from '@kbn/security-solut import { ENABLE_ASSET_CRITICALITY_SETTING } from '@kbn/security-solution-plugin/common/constants'; import { createRule } from '../../../../../../../common/utils/security_solution'; import { - getOpenAlerts, + getAlerts, getPreviewAlerts, getThresholdRuleForAlertTesting, previewRule, @@ -66,7 +66,7 @@ export default ({ getService }: FtrProviderContext) => { }, }; const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); expect(alerts.hits.hits.length).toEqual(1); const fullAlert = alerts.hits.hits[0]._source; if (!fullAlert) { @@ -332,7 +332,7 @@ export default ({ getService }: FtrProviderContext) => { }, }; const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); expect(alerts.hits.hits.length).toEqual(1); }); diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts index 29098b7daeffb..9a5763e95c51c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/execution_logic/threshold_alert_suppression.ts @@ -25,7 +25,7 @@ import { RuleExecutionStatusEnum } from '@kbn/security-solution-plugin/common/ap import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; import { createRule } from '../../../../../../../common/utils/security_solution'; import { - getOpenAlerts, + getAlerts, getPreviewAlerts, getThresholdRuleForAlertTesting, previewRule, @@ -85,7 +85,7 @@ export default ({ getService }: FtrProviderContext) => { interval: '30m', }; const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); expect(alerts.hits.hits.length).toEqual(1); // suppression start equal to alert timestamp @@ -122,7 +122,7 @@ export default ({ getService }: FtrProviderContext) => { await patchRule(supertest, log, { id: createdRule.id, enabled: false }); await patchRule(supertest, log, { id: createdRule.id, enabled: true }); const afterTimestamp = new Date(); - const secondAlerts = await getOpenAlerts( + const secondAlerts = await getAlerts( supertest, log, es, @@ -177,7 +177,7 @@ export default ({ getService }: FtrProviderContext) => { interval: '30m', }; const createdRule = await createRule(supertest, log, rule); - const alerts = await getOpenAlerts(supertest, log, es, createdRule); + const alerts = await getAlerts(supertest, log, es, createdRule); // Close the alert. Subsequent rule executions should ignore this closed alert // for suppression purposes. @@ -202,7 +202,7 @@ export default ({ getService }: FtrProviderContext) => { await patchRule(supertest, log, { id: createdRule.id, enabled: false }); await patchRule(supertest, log, { id: createdRule.id, enabled: true }); const afterTimestamp = new Date(); - const secondAlerts = await getOpenAlerts( + const secondAlerts = await getAlerts( supertest, log, es, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/timestamps.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/timestamps.ts index 6ae4964ece45b..108066830c636 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/timestamps.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/detection_engine/rule_execution_logic/trial_license_complete_tier/timestamps.ts @@ -14,7 +14,7 @@ import { } from '@kbn/security-solution-plugin/common/api/detection_engine'; import { ALERT_ORIGINAL_TIME } from '@kbn/security-solution-plugin/common/field_maps/field_names'; -import { getOpenAlerts, getEqlRuleForAlertTesting } from '../../../utils'; +import { getAlerts, getEqlRuleForAlertTesting } from '../../../utils'; import { createAlertsIndex, deleteAllRules, @@ -251,7 +251,7 @@ export default ({ getService }: FtrProviderContext) => { }; const createdRule = await createRule(supertest, log, rule); - const alertsOpen = await getOpenAlerts( + const alertsOpen = await getAlerts( supertest, log, es, @@ -330,7 +330,7 @@ export default ({ getService }: FtrProviderContext) => { timestamp_override_fallback_disabled: true, }; const createdRule = await createRule(supertest, log, rule); - const alertsOpen = await getOpenAlerts( + const alertsOpen = await getAlerts( supertest, log, es, diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts.ts new file mode 100644 index 0000000000000..21d8233c8496b --- /dev/null +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_alerts.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type SuperTest from 'supertest'; +import type { Client } from '@elastic/elasticsearch'; +import type { ToolingLog } from '@kbn/tooling-log'; +import { + RuleExecutionStatus, + RuleExecutionStatusEnum, +} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; +import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; + +import { refreshIndex } from '..'; +import { getAlertsByIds, waitForRuleStatus } from '../../../../../common/utils/security_solution'; + +export type GetAlerts = ( + supertest: SuperTest.SuperTest, + log: ToolingLog, + es: Client, + rule: RuleResponse, + status?: RuleExecutionStatus, + size?: number, + afterDate?: Date +) => ReturnType; + +/** + * returns all alerts: opened and closed + */ +export const getAlerts: GetAlerts = async ( + supertest, + log, + es, + rule, + status = RuleExecutionStatusEnum.succeeded, + size, + afterDate +) => { + await waitForRuleStatus(status, { supertest, log, id: rule.id, afterDate }); + // Critically important that we wait for rule success AND refresh the write index in that order before we + // assert that no Alerts were created. Otherwise, Alerts could be written but not available to query yet + // when we search, causing tests that check that Alerts are NOT created to pass when they should fail. + await refreshIndex(es, '.alerts-security.alerts-default*'); + return getAlertsByIds(supertest, log, [rule.id], size); +}; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts index 66370e6236bf9..3f5fdf44a192c 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/get_open_alerts.ts @@ -5,31 +5,19 @@ * 2.0. */ -import type SuperTest from 'supertest'; -import type { Client } from '@elastic/elasticsearch'; -import type { ToolingLog } from '@kbn/tooling-log'; -import { - RuleExecutionStatus, - RuleExecutionStatusEnum, -} from '@kbn/security-solution-plugin/common/api/detection_engine/rule_monitoring'; -import type { RuleResponse } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; +import { getAlerts } from './get_alerts'; +import type { GetAlerts } from './get_alerts'; -import { refreshIndex } from '..'; -import { getAlertsByIds, waitForRuleStatus } from '../../../../../common/utils/security_solution'; +/** + * returns only alerts with open status + */ +export const getOpenAlerts: GetAlerts = async (...args) => { + const alerts = await getAlerts(...args); + + alerts.hits.hits = alerts.hits.hits.filter( + (alert) => alert?._source?.[ALERT_WORKFLOW_STATUS] === 'open' + ); -export const getOpenAlerts = async ( - supertest: SuperTest.SuperTest, - log: ToolingLog, - es: Client, - rule: RuleResponse, - status: RuleExecutionStatus = RuleExecutionStatusEnum.succeeded, - size?: number, - afterDate?: Date -) => { - await waitForRuleStatus(status, { supertest, log, id: rule.id, afterDate }); - // Critically important that we wait for rule success AND refresh the write index in that order before we - // assert that no Alerts were created. Otherwise, Alerts could be written but not available to query yet - // when we search, causing tests that check that Alerts are NOT created to pass when they should fail. - await refreshIndex(es, '.alerts-security.alerts-default*'); - return getAlertsByIds(supertest, log, [rule.id], size); + return alerts; }; diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts index 2dba313d1bbcf..1633b5c67f7a4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/utils/alerts/index.ts @@ -7,6 +7,7 @@ export * from './wait_for_alert_to_complete'; export * from './wait_for_alert_to_complete'; +export * from './get_alerts'; export * from './get_open_alerts'; export * from './remove_random_valued_properties_from_alert'; export * from './set_alert_status'; diff --git a/x-pack/test/security_solution_cypress/config.ts b/x-pack/test/security_solution_cypress/config.ts index fb34362f7fb9b..364327bf83b06 100644 --- a/x-pack/test/security_solution_cypress/config.ts +++ b/x-pack/test/security_solution_cypress/config.ts @@ -45,6 +45,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { '--xpack.alerting.rules.minimumScheduleInterval.value=1s', '--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true', `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'alertSuppressionForNewTermsRuleEnabled', 'chartEmbeddablesEnabled', ])}`, // mock cloud to enable the guided onboarding tour in e2e tests diff --git a/x-pack/test/security_solution_cypress/cypress/README.md b/x-pack/test/security_solution_cypress/cypress/README.md index dea0f72240d1a..d7ec660e1b24d 100644 --- a/x-pack/test/security_solution_cypress/cypress/README.md +++ b/x-pack/test/security_solution_cypress/cypress/README.md @@ -303,7 +303,10 @@ For test developing or test debugging purposes, you need to modify the configura ### Running serverless tests locally pointing to a MKI project created in QA environment (Second Quality Gate) -Run the tests with the following yarn scripts from `x-pack/test/security_solution_cypress`: +Note that when using any of the below scripts, the tests are going to be executed through an MKI project with the version that is currently available in QA. If you need to use +a specific commit (i.e. debugging a failing tests on the periodic pipeline), check the section: `Running serverless tests locally pointing to a MKI project created in QA environment with an overridden image`. + +Run the tests with the following yarn scripts from `x-pack/test/security_solution_cypress`: | Script Name | Description | | ----------- | ----------- | @@ -312,9 +315,9 @@ Run the tests with the following yarn scripts from `x-pack/test/security_solutio | cypress:run:qa:serverless:explore | Runs all tests tagged as SERVERLESS in the `e2e/explore` directory in headless mode using the QA environment and real MKI prorjects. | | cypress:run:qa:serverless:investigations | Runs all tests tagged as SERVERLESS in the `e2e/investigations` directory in headless mode using the QA environment and reak MKI projects. | | cypress:run:qa:serverless:rule_management | Runs all tests tagged as SERVERLESS in the `e2e/detection_response/rule_management` directory, excluding `e2e/detection_response/rule_management/prebuilt_rules` in headless mode using the QA environment and reak MKI projects. | -| cypress:run:qa:serverless:rule_management:prebuilt_rules | Runs all tests tagged as SERVERLESS in the `e2e/detection_response/rule_management/prebuilt_rules` directory in headless mode using the QA environment and reak MKI projects. | +| cypress:run:qa:serverless:rule_management:prebuilt_rules | Runs all tests tagged as SERVERLESS in the `e2e/detection_response/rule_management/prebuilt_rules` directory in headless mode using the QA environment and real MKI projects. | | cypress:run:qa:serverless:detection_engine | Runs all tests tagged as SERVERLESS in the `e2e/detection_response/detection_engine` directory, excluding `e2e/detection_response/detection_engine/exceptions` in headless mode using the QA environment and reak MKI projects. | -| cypress:run:qa:serverless:detection_engine:prebuilt_rules | Runs all tests tagged as SERVERLESS in the `e2e/detection_response/detection_engine/exceptions` directory in headless mode using the QA environment and reak MKI projects. | +| cypress:run:qa:serverless:detection_engine:prebuilt_rules | Runs all tests tagged as SERVERLESS in the `e2e/detection_response/detection_engine/exceptions` directory in headless mode using the QA environment and real MKI projects. | | cypress:run:qa:serverless:ai_assistant | Runs all tests tagged as SERVERLESS in the `e2e/ai_assistant` directory in headless mode using the QA environment and reak MKI projects. | @@ -355,6 +358,19 @@ Store the email and password of the account you used to login in the QA Environm } ``` +### Running serverless tests locally pointing to a MKI project created in QA environment with an overridden image + +In order to execute Cypress using a project wih an overridden image, you need to first make sure that the image is created (if you need to debug a failing test of the periodic pipeline, the image is already created you just need to check the commit that was used). + +In order to check for the existance of an image check: https://container-library.elastic.co/r/kibana-ci, if the image with the commit you want to use does not exist **DON'T USE THE COMMIT FLAG SINCE YOU MAY CAUSE A MAJOR ISSUE IN CONTROL PLANE > CAUSE A BULK OF ALERTS IN CONTROL PLANE** and the project will not be functional. + +You need to have everything setup as mentioned above in `Setup required`. Once the setup is ready you just need to execute Cypress with the following option: + +``` +yarn cypress:open:qa:serverless --commit +``` + + #### Testing with different roles If you want to execute a test using Cypress on visual mode with MKI, you need to make sure you have the user created in your organization, and add it tot he `.ftr/role_users.json`: diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_suppression_serverless_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_suppression_serverless_essentials.cy.ts new file mode 100644 index 0000000000000..a1aae6c8d7b49 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_suppression_serverless_essentials.cy.ts @@ -0,0 +1,60 @@ +/* + * 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 { + selectThresholdRuleType, + selectIndicatorMatchType, + selectNewTermsRuleType, +} from '../../../../tasks/create_new_rule'; +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; +import { + ALERT_SUPPRESSION_FIELDS_INPUT, + THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX, +} from '../../../../screens/create_new_rule'; +import { CREATE_RULE_URL } from '../../../../urls/navigation'; + +describe( + 'Detection rules, Alert Suppression for Essentials tier', + { + tags: ['@serverless'], + env: { + ftrConfig: { + productTypes: [ + { product_line: 'security', product_tier: 'essentials' }, + { product_line: 'endpoint', product_tier: 'essentials' }, + ], + // alertSuppressionForNewTermsRuleEnabled feature flag is also enabled in a global config + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'alertSuppressionForNewTermsRuleEnabled', + ])}`, + ], + }, + }, + }, + () => { + beforeEach(() => { + login(); + visit(CREATE_RULE_URL); + }); + + it('Alert suppression is enabled for essentials tier for rule types that support it', () => { + // default custom query rule + cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).should('be.enabled'); + + selectIndicatorMatchType(); + cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).should('be.enabled'); + + selectNewTermsRuleType(); + cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).should('be.enabled'); + + selectThresholdRuleType(); + cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).should('be.enabled'); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_supression_ess_basic.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_supression_ess_basic.cy.ts new file mode 100644 index 0000000000000..fbcc43e4652ae --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/common_flows_supression_ess_basic.cy.ts @@ -0,0 +1,62 @@ +/* + * 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 { + THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX, + ALERT_SUPPRESSION_DURATION_INPUT, +} from '../../../../screens/create_new_rule'; + +import { + selectIndicatorMatchType, + selectNewTermsRuleType, + selectThresholdRuleType, + openSuppressionFieldsTooltipAndCheckLicense, +} from '../../../../tasks/create_new_rule'; +import { startBasicLicense } from '../../../../tasks/api_calls/licensing'; +import { login } from '../../../../tasks/login'; +import { visit } from '../../../../tasks/navigation'; +import { CREATE_RULE_URL } from '../../../../urls/navigation'; +import { TOOLTIP } from '../../../../screens/common'; + +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; + +describe( + 'Detection rules, Common flows Alert Suppression', + { + tags: ['@ess'], + }, + () => { + describe('Create rule form', () => { + beforeEach(() => { + deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); + startBasicLicense(); + }); + + it('can not create rule with rule execution suppression on basic license for all rules with enabled suppression', () => { + // Default query rule + openSuppressionFieldsTooltipAndCheckLicense(); + + selectIndicatorMatchType(); + openSuppressionFieldsTooltipAndCheckLicense(); + + selectNewTermsRuleType(); + openSuppressionFieldsTooltipAndCheckLicense(); + + selectThresholdRuleType(); + cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).should('be.disabled'); + cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).parent().trigger('mouseover'); + // Platinum license is required, tooltip on disabled alert suppression checkbox should tell this + cy.get(TOOLTIP).contains('Platinum license'); + + cy.get(ALERT_SUPPRESSION_DURATION_INPUT).eq(0).should('be.disabled'); + cy.get(ALERT_SUPPRESSION_DURATION_INPUT).eq(1).should('be.disabled'); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression_ess_basic.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression_ess_basic.cy.ts index 17c1e57fd85ed..dd3c086224e49 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression_ess_basic.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/indicator_match_rule_suppression_ess_basic.cy.ts @@ -7,10 +7,6 @@ import { getNewThreatIndicatorRule } from '../../../../objects/rule'; -import { - ALERT_SUPPRESSION_FIELDS_INPUT, - ALERT_SUPPRESSION_FIELDS, -} from '../../../../screens/create_new_rule'; import { SUPPRESS_FOR_DETAILS, DETAILS_TITLE, @@ -20,7 +16,6 @@ import { ALERT_SUPPRESSION_INSUFFICIENT_LICENSING_ICON, } from '../../../../screens/rule_details'; -import { selectIndicatorMatchType } from '../../../../tasks/create_new_rule'; import { startBasicLicense } from '../../../../tasks/api_calls/licensing'; import { createRule } from '../../../../tasks/api_calls/rules'; import { login } from '../../../../tasks/login'; @@ -47,16 +42,6 @@ describe( startBasicLicense(); }); - it('can not create rule with rule execution suppression on basic license', () => { - selectIndicatorMatchType(); - - cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).should('be.disabled'); - cy.get(ALERT_SUPPRESSION_FIELDS).trigger('mouseover'); - - // Platinum license is required, tooltip on disabled alert suppression checkbox should tell this - cy.get(TOOLTIP).contains('Platinum license'); - }); - it('shows upselling message on rule details with suppression on basic license', () => { const rule = getNewThreatIndicatorRule(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/new_terms_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/new_terms_rule.cy.ts index 3b77522dfbff5..cb05087f008aa 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/new_terms_rule.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/new_terms_rule.cy.ts @@ -41,6 +41,9 @@ import { TIMELINE_TEMPLATE_DETAILS, NEW_TERMS_HISTORY_WINDOW_DETAILS, NEW_TERMS_FIELDS_DETAILS, + SUPPRESS_BY_DETAILS, + SUPPRESS_FOR_DETAILS, + SUPPRESS_MISSING_FIELD, } from '../../../../screens/rule_details'; import { getDetails, waitForTheRuleToBeExecuted } from '../../../../tasks/rule_details'; @@ -49,94 +52,153 @@ import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; import { createAndEnableRule, fillAboutRuleAndContinue, + fillDefineNewTermsRule, fillDefineNewTermsRuleAndContinue, fillScheduleRuleAndContinue, selectNewTermsRuleType, waitForAlertsToPopulate, + fillAlertSuppressionFields, + fillAboutRuleMinimumAndContinue, + createRuleWithoutEnabling, + skipScheduleRuleAction, + continueFromDefineStep, + selectAlertSuppressionPerInterval, + setAlertSuppressionDuration, + selectDoNotSuppressForMissingFields, } from '../../../../tasks/create_new_rule'; import { login } from '../../../../tasks/login'; import { visit } from '../../../../tasks/navigation'; import { CREATE_RULE_URL } from '../../../../urls/navigation'; import { openRuleManagementPageViaBreadcrumbs } from '../../../../tasks/rules_management'; -describe('New Terms rules', { tags: ['@ess', '@serverless'] }, () => { - describe('Detection rules, New Terms', () => { - const rule = getNewTermsRule(); - const expectedUrls = rule.references?.join(''); - const expectedFalsePositives = rule.false_positives?.join(''); - const expectedTags = rule.tags?.join(''); - const mitreAttack = rule.threat; - const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []); - const expectedNumberOfRules = 1; - - beforeEach(() => { - deleteAlertsAndRules(); - login(); - }); +describe( + 'New Terms rules', + { + tags: ['@ess', '@serverless'], + env: { + // alertSuppressionForNewTermsRuleEnabled feature flag is also enabled in a global config + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'alertSuppressionForNewTermsRuleEnabled', + ])}`, + ], + }, + }, + () => { + describe('Detection rules, New Terms', () => { + const rule = getNewTermsRule(); + const expectedUrls = rule.references?.join(''); + const expectedFalsePositives = rule.false_positives?.join(''); + const expectedTags = rule.tags?.join(''); + const mitreAttack = rule.threat; + const expectedMitre = formatMitreAttackDescription(mitreAttack ?? []); + const expectedNumberOfRules = 1; + + beforeEach(() => { + deleteAlertsAndRules(); + login(); + visit(CREATE_RULE_URL); + selectNewTermsRuleType(); + }); + + it('Creates and enables a new terms rule', function () { + fillDefineNewTermsRuleAndContinue(rule); + fillAboutRuleAndContinue(rule); + fillScheduleRuleAndContinue(rule); + createAndEnableRule(); + openRuleManagementPageViaBreadcrumbs(); + + cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - it('Creates and enables a new terms rule', function () { - visit(CREATE_RULE_URL); - selectNewTermsRuleType(); - fillDefineNewTermsRuleAndContinue(rule); - fillAboutRuleAndContinue(rule); - fillScheduleRuleAndContinue(rule); - createAndEnableRule(); - openRuleManagementPageViaBreadcrumbs(); - - cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)'); - - expectNumberOfRules(RULES_MANAGEMENT_TABLE, expectedNumberOfRules); - - cy.get(RULE_NAME).should('have.text', rule.name); - cy.get(RISK_SCORE).should('have.text', rule.risk_score); - cy.get(SEVERITY).should('have.text', 'High'); - cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); - - goToRuleDetailsOf(rule.name); - - cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); - cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); - cy.get(ABOUT_DETAILS).within(() => { - getDetails(SEVERITY_DETAILS).should('have.text', 'High'); - getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score); - getDetails(REFERENCE_URLS_DETAILS).should((details) => { - expect(removeExternalLinkText(details.text())).equal(expectedUrls); + expectNumberOfRules(RULES_MANAGEMENT_TABLE, expectedNumberOfRules); + + cy.get(RULE_NAME).should('have.text', rule.name); + cy.get(RISK_SCORE).should('have.text', rule.risk_score); + cy.get(SEVERITY).should('have.text', 'High'); + cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true'); + + goToRuleDetailsOf(rule.name); + + cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`); + cy.get(ABOUT_RULE_DESCRIPTION).should('have.text', rule.description); + cy.get(ABOUT_DETAILS).within(() => { + getDetails(SEVERITY_DETAILS).should('have.text', 'High'); + getDetails(RISK_SCORE_DETAILS).should('have.text', rule.risk_score); + getDetails(REFERENCE_URLS_DETAILS).should((details) => { + expect(removeExternalLinkText(details.text())).equal(expectedUrls); + }); + getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); + getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { + expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + }); + getDetails(TAGS_DETAILS).should('have.text', expectedTags); }); - getDetails(FALSE_POSITIVES_DETAILS).should('have.text', expectedFalsePositives); - getDetails(MITRE_ATTACK_DETAILS).should((mitre) => { - expect(removeExternalLinkText(mitre.text())).equal(expectedMitre); + cy.get(INVESTIGATION_NOTES_TOGGLE).click(); + cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join('')); + getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.query); + getDetails(RULE_TYPE_DETAILS).should('have.text', 'New Terms'); + getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); + getDetails(NEW_TERMS_FIELDS_DETAILS).should('have.text', 'host.name'); + getDetails(NEW_TERMS_HISTORY_WINDOW_DETAILS).should('have.text', '51000h'); }); - getDetails(TAGS_DETAILS).should('have.text', expectedTags); - }); - cy.get(INVESTIGATION_NOTES_TOGGLE).click(); - cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN); - cy.get(DEFINITION_DETAILS).within(() => { - getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join('')); - getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.query); - getDetails(RULE_TYPE_DETAILS).should('have.text', 'New Terms'); - getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None'); - getDetails(NEW_TERMS_FIELDS_DETAILS).should('have.text', 'host.name'); - getDetails(NEW_TERMS_HISTORY_WINDOW_DETAILS).should('have.text', '51000h'); - }); - cy.get(SCHEDULE_DETAILS).within(() => { - getDetails(RUNS_EVERY_DETAILS).should('have.text', `${rule.interval}`); - const humanizedDuration = getHumanizedDuration( - rule.from ?? 'now-6m', - rule.interval ?? '5m' - ); - getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should('have.text', `${humanizedDuration}`); + cy.get(SCHEDULE_DETAILS).within(() => { + getDetails(RUNS_EVERY_DETAILS).should('have.text', `${rule.interval}`); + const humanizedDuration = getHumanizedDuration( + rule.from ?? 'now-6m', + rule.interval ?? '5m' + ); + getDetails(ADDITIONAL_LOOK_BACK_DETAILS).should('have.text', `${humanizedDuration}`); + }); + + waitForTheRuleToBeExecuted(); + waitForAlertsToPopulate(); + + cy.get(ALERT_DATA_GRID) + .invoke('text') + .then((text) => { + expect(text).contains(rule.name); + expect(text).contains(rule.severity); + expect(text).contains(rule.risk_score); + }); }); - waitForTheRuleToBeExecuted(); - waitForAlertsToPopulate(); + it('Creates rule rule with time interval suppression', () => { + const SUPPRESS_BY_FIELDS = ['agent.hostname', 'agent.type']; + + fillDefineNewTermsRule(rule); + + // fill suppress by fields and select non-default suppression options + fillAlertSuppressionFields(SUPPRESS_BY_FIELDS); + selectAlertSuppressionPerInterval(); + setAlertSuppressionDuration(45, 'm'); + selectDoNotSuppressForMissingFields(); + continueFromDefineStep(); - cy.get(ALERT_DATA_GRID) - .invoke('text') - .then((text) => { - expect(text).contains(rule.name); - expect(text).contains(rule.severity); - expect(text).contains(rule.risk_score); + // ensures details preview works correctly + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '45m'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Do not suppress alerts for events with missing fields' + ); }); + + fillAboutRuleMinimumAndContinue(rule); + skipScheduleRuleAction(); + createRuleWithoutEnabling(); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', '45m'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Do not suppress alerts for events with missing fields' + ); + }); + }); }); - }); -}); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule_ess_basic.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule_ess_basic.cy.ts deleted file mode 100644 index 39328f560bff8..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule_ess_basic.cy.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - ALERT_SUPPRESSION_DURATION_INPUT, - THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX, -} from '../../../../screens/create_new_rule'; - -import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; -import { startBasicLicense } from '../../../../tasks/api_calls/licensing'; -import { selectThresholdRuleType } from '../../../../tasks/create_new_rule'; -import { login } from '../../../../tasks/login'; -import { visit } from '../../../../tasks/navigation'; -import { CREATE_RULE_URL } from '../../../../urls/navigation'; -import { TOOLTIP } from '../../../../screens/common'; - -describe('Threshold rules, ESS basic license', { tags: ['@ess'] }, () => { - beforeEach(() => { - deleteAlertsAndRules(); - login(); - visit(CREATE_RULE_URL); - startBasicLicense(); - }); - - it('Alert suppression is disabled for basic license', () => { - selectThresholdRuleType(); - - cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).should('be.disabled'); - cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).parent().trigger('mouseover'); - // Platinum license is required, tooltip on disabled alert suppression checkbox should tell this - cy.get(TOOLTIP).contains('Platinum license'); - - cy.get(ALERT_SUPPRESSION_DURATION_INPUT).eq(0).should('be.disabled'); - cy.get(ALERT_SUPPRESSION_DURATION_INPUT).eq(1).should('be.disabled'); - }); -}); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule_serverless_essentials.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule_serverless_essentials.cy.ts deleted file mode 100644 index c26954ea7e37b..0000000000000 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_creation/threshold_rule_serverless_essentials.cy.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX } from '../../../../screens/create_new_rule'; -import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; -import { selectThresholdRuleType } from '../../../../tasks/create_new_rule'; -import { login } from '../../../../tasks/login'; -import { visit } from '../../../../tasks/navigation'; -import { CREATE_RULE_URL } from '../../../../urls/navigation'; - -describe( - 'Threshold rules, Serverless essentials license', - { - tags: ['@serverless'], - - env: { - ftrConfig: { - productTypes: [ - { product_line: 'security', product_tier: 'essentials' }, - { product_line: 'endpoint', product_tier: 'essentials' }, - ], - }, - }, - }, - () => { - beforeEach(() => { - deleteAlertsAndRules(); - login(); - visit(CREATE_RULE_URL); - }); - - it('Alert suppression is enabled for essentials', () => { - selectThresholdRuleType(); - - cy.get(THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX).should('be.enabled'); - }); - } -); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/new_terms_rule.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/new_terms_rule.cy.ts new file mode 100644 index 0000000000000..7604903d2b47e --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/rule_edit/new_terms_rule.cy.ts @@ -0,0 +1,108 @@ +/* + * 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 { getNewTermsRule } from '../../../../objects/rule'; + +import { + SUPPRESS_FOR_DETAILS, + DEFINITION_DETAILS, + SUPPRESS_MISSING_FIELD, + SUPPRESS_BY_DETAILS, +} from '../../../../screens/rule_details'; + +import { + ALERT_SUPPRESSION_DURATION_INPUT, + ALERT_SUPPRESSION_FIELDS, + ALERT_SUPPRESSION_MISSING_FIELDS_SUPPRESS, +} from '../../../../screens/create_new_rule'; + +import { createRule } from '../../../../tasks/api_calls/rules'; + +import { RULES_MANAGEMENT_URL } from '../../../../urls/rules_management'; +import { getDetails } from '../../../../tasks/rule_details'; +import { deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { login } from '../../../../tasks/login'; + +import { editFirstRule } from '../../../../tasks/alerts_detection_rules'; + +import { saveEditedRule } from '../../../../tasks/edit_rule'; +import { + selectAlertSuppressionPerRuleExecution, + selectDoNotSuppressForMissingFields, + fillAlertSuppressionFields, +} from '../../../../tasks/create_new_rule'; +import { visit } from '../../../../tasks/navigation'; + +const SUPPRESS_BY_FIELDS = ['agent.hostname', 'agent.type']; + +const rule = getNewTermsRule(); + +describe( + 'Detection rules, New terms, Edit', + { + tags: ['@ess', '@serverless'], + env: { + // alertSuppressionForNewTermsRuleEnabled feature flag is also enabled in a global config + kbnServerArgs: [ + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'alertSuppressionForNewTermsRuleEnabled', + ])}`, + ], + }, + }, + () => { + beforeEach(() => { + login(); + deleteAlertsAndRules(); + }); + + describe('with suppression configured', () => { + beforeEach(() => { + createRule({ + ...rule, + alert_suppression: { + group_by: SUPPRESS_BY_FIELDS.slice(0, 1), + duration: { value: 20, unit: 'm' }, + missing_fields_strategy: 'suppress', + }, + }); + }); + + it('displays suppress options correctly on edit form and allows its editing', () => { + visit(RULES_MANAGEMENT_URL); + editFirstRule(); + + // check saved suppression settings + cy.get(ALERT_SUPPRESSION_DURATION_INPUT) + .eq(0) + .should('be.enabled') + .should('have.value', 20); + cy.get(ALERT_SUPPRESSION_DURATION_INPUT) + .eq(1) + .should('be.enabled') + .should('have.value', 'm'); + cy.get(ALERT_SUPPRESSION_FIELDS).should('contain', SUPPRESS_BY_FIELDS.slice(0, 1).join('')); + cy.get(ALERT_SUPPRESSION_MISSING_FIELDS_SUPPRESS).should('be.checked'); + + selectAlertSuppressionPerRuleExecution(); + selectDoNotSuppressForMissingFields(); + fillAlertSuppressionFields(SUPPRESS_BY_FIELDS.slice(1)); + + saveEditedRule(); + + cy.get(DEFINITION_DETAILS).within(() => { + getDetails(SUPPRESS_BY_DETAILS).should('have.text', SUPPRESS_BY_FIELDS.join('')); + getDetails(SUPPRESS_FOR_DETAILS).should('have.text', 'One rule execution'); + getDetails(SUPPRESS_MISSING_FIELD).should( + 'have.text', + 'Do not suppress alerts for events with missing fields' + ); + }); + }); + }); + } +); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/sourcerer/sourcerer.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/sourcerer/sourcerer.cy.ts index 50cc4c3995684..044318b16826d 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/sourcerer/sourcerer.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/sourcerer/sourcerer.cy.ts @@ -33,7 +33,7 @@ const siemDataViewTitle = 'Security Default Data View'; const dataViews = ['auditbeat-*,fakebeat-*', 'auditbeat-*,*beat*,siem-read*,.kibana*,fakebeat-*']; // FLAKY: https://github.com/elastic/kibana/issues/177080 -describe.skip('Sourcerer', { tags: ['@ess', '@serverless'] }, () => { +describe('Sourcerer', { tags: ['@ess', '@serverless'] }, () => { before(() => { dataViews.forEach((dataView: string) => postDataView(dataView)); }); @@ -53,7 +53,7 @@ describe.skip('Sourcerer', { tags: ['@ess', '@serverless'] }, () => { }); // FLAKY: https://github.com/elastic/kibana/issues/177586 - describe.skip('Modified badge', () => { + describe('Modified badge', () => { it('Selecting new data view does not add a modified badge', () => { cy.get(SOURCERER.badgeModified).should(`not.exist`); openSourcerer(); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/sourcerer/sourcerer_timeline.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/sourcerer/sourcerer_timeline.cy.ts index b99b93dd956ee..7265f30b76f78 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/sourcerer/sourcerer_timeline.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/detection_engine/sourcerer/sourcerer_timeline.cy.ts @@ -31,6 +31,7 @@ import { saveSourcerer, } from '../../../../tasks/sourcerer'; import { openTimelineUsingToggle } from '../../../../tasks/security_main'; +import { waitForFleetSetup } from '../../../../tasks/fleet_integrations'; import { SOURCERER } from '../../../../screens/sourcerer'; import { createTimeline, deleteTimelines } from '../../../../tasks/api_calls/timelines'; import { getTimelineModifiedSourcerer } from '../../../../objects/timeline'; @@ -40,6 +41,10 @@ const siemDataViewTitle = 'Security Default Data View'; const dataViews = ['logs-*', 'metrics-*', '.kibana-event-log-*']; describe('Timeline scope', { tags: ['@ess', '@serverless', '@skipInServerless'] }, () => { + before(() => { + waitForFleetSetup(); + }); + beforeEach(() => { cy.clearLocalStorage(); login(); @@ -58,7 +63,7 @@ describe('Timeline scope', { tags: ['@ess', '@serverless', '@skipInServerless'] }); // FLAKY: https://github.com/elastic/kibana/issues/173854 - describe.skip('Modified badge', () => { + describe('Modified badge', () => { it('Selecting new data view does not add a modified badge', () => { openTimelineUsingToggle(); cy.get(SOURCERER.badgeModified).should(`not.exist`); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/prebuilt_rules_preview.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/prebuilt_rules_preview.cy.ts index db39136abc963..2a29e477447b5 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/prebuilt_rules_preview.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/detection_response/rule_management/prebuilt_rules/prebuilt_rules_preview.cy.ts @@ -326,6 +326,7 @@ describe('Detection rules, Prebuilt Rules Installation and Update workflow', () $state: { store: 'appState' }, }, ], + alert_suppression: undefined, }); const ESQL_RULE = createRuleAssetSavedObject({ diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts index 1a85088a240a1..b3a5a39dbb186 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/create_new_rule.ts @@ -32,6 +32,8 @@ import { convertHistoryStartToSize, getHumanizedDuration } from '../helpers/rule import { ABOUT_CONTINUE_BTN, ALERT_SUPPRESSION_DURATION_INPUT, + ALERT_SUPPRESSION_FIELDS, + ALERT_SUPPRESSION_FIELDS_INPUT, ALERT_SUPPRESSION_FIELDS_COMBO_BOX, ALERT_SUPPRESSION_MISSING_FIELDS_DO_NOT_SUPPRESS, THRESHOLD_ENABLE_SUPPRESSION_CHECKBOX, @@ -145,6 +147,7 @@ import { ruleFields } from '../data/detection_engine'; import { waitForAlerts } from './alerts'; import { refreshPage } from './security_header'; import { EMPTY_ALERT_TABLE } from '../screens/alerts'; +import { TOOLTIP } from '../screens/common'; export const createAndEnableRule = () => { cy.get(CREATE_AND_ENABLE_BTN).click(); @@ -541,14 +544,12 @@ export const fillDefineEqlRuleAndContinue = (rule: EqlRuleCreateProps) => { cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); }; -export const fillDefineNewTermsRuleAndContinue = (rule: NewTermsRuleCreateProps) => { +export const fillDefineNewTermsRule = (rule: NewTermsRuleCreateProps) => { cy.get(CUSTOM_QUERY_INPUT) .first() .type(rule.query || ''); cy.get(NEW_TERMS_INPUT_AREA).find(INPUT).click(); - cy.get(NEW_TERMS_INPUT_AREA).find(INPUT).type(rule.new_terms_fields[0], { delay: 35 }); - - cy.get(EUI_FILTER_SELECT_ITEM).click(); + cy.get(NEW_TERMS_INPUT_AREA).find(INPUT).type(`${rule.new_terms_fields[0]}{enter}`); cy.focused().type('{esc}'); // Close combobox dropdown so next inputs can be interacted with const historySize = convertHistoryStartToSize(rule.history_window_start); @@ -557,6 +558,10 @@ export const fillDefineNewTermsRuleAndContinue = (rule: NewTermsRuleCreateProps) cy.get(NEW_TERMS_INPUT_AREA).find(NEW_TERMS_HISTORY_SIZE).type('{selectAll}'); cy.get(NEW_TERMS_INPUT_AREA).find(NEW_TERMS_HISTORY_SIZE).type(historySizeNumber); cy.get(NEW_TERMS_INPUT_AREA).find(NEW_TERMS_HISTORY_TIME_TYPE).select(historySizeType); +}; + +export const fillDefineNewTermsRuleAndContinue = (rule: NewTermsRuleCreateProps) => { + fillDefineNewTermsRule(rule); cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); }; @@ -874,6 +879,18 @@ export const setAlertSuppressionDuration = (interval: number, timeUnit: 's' | 'm cy.get(ALERT_SUPPRESSION_DURATION_INPUT).eq(1).select(timeUnit); }; +/** + * Opens tooltip on disabled suppression fields and checks if it contains requirement for Platinum license. + * + * Suppression fields are disabled when app has insufficient license + */ +export const openSuppressionFieldsTooltipAndCheckLicense = () => { + cy.get(ALERT_SUPPRESSION_FIELDS_INPUT).should('be.disabled'); + cy.get(ALERT_SUPPRESSION_FIELDS).trigger('mouseover'); + // Platinum license is required, tooltip on disabled alert suppression checkbox should tell this + cy.get(TOOLTIP).contains('Platinum license'); +}; + export const checkLoadQueryDynamically = () => { cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).click({ force: true }); cy.get(LOAD_QUERY_DYNAMICALLY_CHECKBOX).should('be.checked'); diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts b/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts index bafc80b383547..b60c6a8c5622f 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/fleet_integrations.ts @@ -9,6 +9,8 @@ import { GET_INSTALLED_INTEGRATIONS_URL, InstalledIntegration, } from '@kbn/security-solution-plugin/common/api/detection_engine'; +import { login } from './login'; +import { visitGetStartedPage } from './navigation'; export const mockFleetInstalledIntegrations = (integrations: InstalledIntegration[] = []) => { cy.intercept('GET', `${GET_INSTALLED_INTEGRATIONS_URL}*`, { @@ -18,3 +20,11 @@ export const mockFleetInstalledIntegrations = (integrations: InstalledIntegratio }, }).as('installedIntegrations'); }; + +export const waitForFleetSetup = () => { + cy.intercept('POST', '/api/fleet/epm/packages/_bulk?prerelease=true').as('fleetSetup'); + cy.clearLocalStorage(); + login(); + visitGetStartedPage(); + cy.wait('@fleetSetup'); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts b/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts index 2bf56c1bd7c66..c2ad653d91d76 100644 --- a/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts +++ b/x-pack/test/security_solution_cypress/cypress/tasks/sourcerer.ts @@ -46,7 +46,8 @@ export const isDataViewSelection = (dataView: string) => { return cy.get(SOURCERER.selectActiveOption).should('contain', dataView); }; -export const openDataViewSelection = () => cy.get(SOURCERER.selectActiveOption).click(); +export const openDataViewSelection = () => + cy.get(SOURCERER.selectActiveOption).click({ force: true }); export const isKibanaDataViewOption = (dataViews: string[]) => { return dataViews.every((dataView) => { return cy.get(SOURCERER.selectListOption).should(`contain`, dataView); diff --git a/x-pack/test/security_solution_cypress/serverless_config.ts b/x-pack/test/security_solution_cypress/serverless_config.ts index d0ee1613f6e4c..2edfe8f1e36c9 100644 --- a/x-pack/test/security_solution_cypress/serverless_config.ts +++ b/x-pack/test/security_solution_cypress/serverless_config.ts @@ -34,6 +34,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { { product_line: 'endpoint', product_tier: 'complete' }, { product_line: 'cloud', product_tier: 'complete' }, ])}`, + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'alertSuppressionForNewTermsRuleEnabled', + ])}`, ], }, testRunner: SecuritySolutionConfigurableCypressTestRunner, diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index 6f9c89bd8c930..e4d9b91e579b7 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -87,7 +87,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // FLAKY: https://github.com/elastic/kibana/issues/170357 // FLAKY: https://github.com/elastic/kibana/issues/173670 - describe('endpoint list', function () { + describe.skip('endpoint list', function () { targetTags(this, ['@ess', '@serverless']); let indexedData: IndexedHostsAndAlertsResponse; diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 6786f82a6fabe..58e1eec24b423 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -16,7 +16,8 @@ import { export default function (providerContext: FtrProviderContext) { const { loadTestFile, getService, getPageObjects } = providerContext; - describe('endpoint', function () { + // FLAKY: https://github.com/elastic/kibana/issues/180401 + describe.skip('endpoint', function () { const ingestManager = getService('ingestManager'); const log = getService('log'); const endpointTestResources = getService('endpointTestResources'); diff --git a/x-pack/test/security_solution_endpoint/apps/integrations/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/integrations/policy_details.ts index f4d587daf64f1..a2b1feaabd7cd 100644 --- a/x-pack/test/security_solution_endpoint/apps/integrations/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/integrations/policy_details.ts @@ -30,7 +30,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { // FLAKY: https://github.com/elastic/kibana/issues/171653 // FLAKY: https://github.com/elastic/kibana/issues/171654 - describe('When on the Endpoint Policy Details Page', function () { + describe.skip('When on the Endpoint Policy Details Page', function () { targetTags(this, ['@ess', '@serverless']); let indexedData: IndexedHostsAndAlertsResponse; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts index 1234c45f9210f..f8019154206e0 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts @@ -16,7 +16,8 @@ export default function ({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const endpointTestResources = getService('endpointTestResources'); - describe('Endpoint `execute` response action', function () { + // FLAKY: https://github.com/elastic/kibana/issues/171666 + describe.skip('Endpoint `execute` response action', function () { targetTags(this, ['@ess', '@serverless']); let indexedData: IndexedHostsAndAlertsResponse; diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/telemetry/telemetry_config.ts b/x-pack/test_serverless/api_integration/test_suites/observability/telemetry/telemetry_config.ts index 78732f6b52826..8fdeb64ac1c19 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/telemetry/telemetry_config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/telemetry/telemetry_config.ts @@ -12,7 +12,9 @@ export default function telemetryConfigTest({ getService }: FtrProviderContext) const svlCommonApi = getService('svlCommonApi'); const supertest = getService('supertest'); - describe('/api/telemetry/v2/config API Telemetry config', () => { + // failsOnMKI, see https://github.com/elastic/kibana/issues/180348 + describe('/api/telemetry/v2/config API Telemetry config', function () { + this.tags(['failsOnMKI']); const baseConfig = { allowChangingOptInStatus: false, optIn: true, diff --git a/x-pack/test_serverless/api_integration/test_suites/security/telemetry/telemetry_config.ts b/x-pack/test_serverless/api_integration/test_suites/security/telemetry/telemetry_config.ts index 41a02950c4f0f..6701fc93a506a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/telemetry/telemetry_config.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/telemetry/telemetry_config.ts @@ -12,7 +12,10 @@ export default function telemetryConfigTest({ getService }: FtrProviderContext) const svlCommonApi = getService('svlCommonApi'); const supertest = getService('supertest'); - describe('/api/telemetry/v2/config API Telemetry config', () => { + // failsOnMKI, see https://github.com/elastic/kibana/issues/180348 + describe('/api/telemetry/v2/config API Telemetry config', function () { + this.tags(['failsOnMKI']); + const baseConfig = { allowChangingOptInStatus: false, optIn: true, diff --git a/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group3.ts b/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group3.ts index 639429eaabd7d..acd34f2efcdfb 100644 --- a/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group3.ts +++ b/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group3.ts @@ -14,7 +14,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...baseTestConfig.getAll(), testFiles: [require.resolve('../../common/visualizations/group2')], junit: { - reportName: 'Serverless Search Functional Tests - Common Group 2', + reportName: 'Serverless Search Functional Tests - Common Group 3', }, }; } diff --git a/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group4.ts b/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group4.ts index 31f9f64449587..8e787b95c5fd8 100644 --- a/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group4.ts +++ b/x-pack/test_serverless/functional/test_suites/search/common_configs/config.group4.ts @@ -14,7 +14,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ...baseTestConfig.getAll(), testFiles: [require.resolve('../../common/visualizations/group3')], junit: { - reportName: 'Serverless Search Functional Tests - Common Group 2', + reportName: 'Serverless Search Functional Tests - Common Group 4', }, }; } diff --git a/yarn.lock b/yarn.lock index 01a86c1aa0031..02456e91ca840 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10812,6 +10812,15 @@ "@types/node" "*" chokidar "^2.1.2" +"@types/webpack-bundle-analyzer@^4.7.0": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz#fe199e724ce3d38705f6f1ba4d62429b7c360541" + integrity sha512-c5i2ThslSNSG8W891BRvOd/RoCjI2zwph8maD22b1adtSns20j+0azDDMCK06DiVrzTgnwiDl5Ntmu1YRJw8Sg== + dependencies: + "@types/node" "*" + tapable "^2.2.0" + webpack "^5" + "@types/webpack-env@^1.15.3", "@types/webpack-env@^1.16.0": version "1.16.3" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.3.tgz#b776327a73e561b71e7881d0cd6d34a1424db86a"