diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.test.mocks.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.test.mocks.ts deleted file mode 100644 index b85e06bde59bc..0000000000000 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.test.mocks.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export const getAliasActionsMock = jest.fn(); - -jest.doMock('../../utils', () => { - const realModule = jest.requireActual('../../utils'); - return { - ...realModule, - getAliasActions: getAliasActionsMock, - }; -}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.test.ts index bcb482a99f9a5..b78b0caac8506 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.test.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { getAliasActionsMock } from './create_target_index.test.mocks'; import * as Either from 'fp-ts/lib/Either'; import { createContextMock, @@ -24,12 +23,12 @@ describe('Stage: createTargetIndex', () => { ...createPostInitState(), controlState: 'CREATE_TARGET_INDEX', indexMappings: { properties: { foo: { type: 'text' } }, _meta: {} }, + creationAliases: [], ...parts, }); beforeEach(() => { context = createContextMock(); - getAliasActionsMock.mockReset().mockReturnValue([]); }); describe('In case of left return', () => { @@ -68,50 +67,11 @@ describe('Stage: createTargetIndex', () => { }); describe('In case of right return', () => { - it('calls getAliasActions with the correct parameters', () => { - const state = createState(); - const res: StateActionResponse<'CREATE_TARGET_INDEX'> = - Either.right('create_index_succeeded'); - - createTargetIndex(state, res, context); - - expect(getAliasActionsMock).toHaveBeenCalledTimes(1); - expect(getAliasActionsMock).toHaveBeenCalledWith({ - currentIndex: state.currentIndex, - existingAliases: [], - indexPrefix: context.indexPrefix, - kibanaVersion: context.kibanaVersion, - }); - }); - - it('CREATE_TARGET_INDEX -> UPDATE_ALIASES when successful and alias actions are not empty', () => { - const state = createState(); - const res: StateActionResponse<'CREATE_TARGET_INDEX'> = - Either.right('create_index_succeeded'); - - const aliasActions = [{ add: { index: '.kibana_1', alias: '.kibana' } }]; - getAliasActionsMock.mockReturnValue(aliasActions); - - const newState = createTargetIndex(state, res, context); - - expect(newState).toEqual({ - ...state, - controlState: 'UPDATE_ALIASES', - previousMappings: state.indexMappings, - currentIndexMeta: state.indexMappings._meta, - aliases: [], - aliasActions, - skipDocumentMigration: true, - }); - }); - - it('CREATE_TARGET_INDEX -> INDEX_STATE_UPDATE_DONE when successful and alias actions are empty', () => { - const state = createState(); + it('CREATE_TARGET_INDEX -> INDEX_STATE_UPDATE_DONE when successful', () => { + const state = createState({}); const res: StateActionResponse<'CREATE_TARGET_INDEX'> = Either.right('create_index_succeeded'); - getAliasActionsMock.mockReturnValue([]); - const newState = createTargetIndex(state, res, context); expect(newState).toEqual({ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.ts index 16f8dd6e5ede7..b7c5081343ed5 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/create_target_index.ts @@ -12,12 +12,11 @@ import { delayRetryState } from '../../../model/retry_state'; import { throwBadResponse } from '../../../model/helpers'; import { CLUSTER_SHARD_LIMIT_EXCEEDED_REASON } from '../../../common/constants'; import { isTypeof } from '../../actions'; -import { getAliasActions } from '../../utils'; import type { ModelStage } from '../types'; export const createTargetIndex: ModelStage< 'CREATE_TARGET_INDEX', - 'UPDATE_ALIASES' | 'INDEX_STATE_UPDATE_DONE' | 'FATAL' + 'INDEX_STATE_UPDATE_DONE' | 'FATAL' > = (state, res, context) => { if (Either.isLeft(res)) { const left = res.left; @@ -36,22 +35,15 @@ export const createTargetIndex: ModelStage< } } - const aliasActions = getAliasActions({ - currentIndex: state.currentIndex, - existingAliases: [], - indexPrefix: context.indexPrefix, - kibanaVersion: context.kibanaVersion, - }); - const currentIndexMeta = cloneDeep(state.indexMappings._meta!); return { ...state, - controlState: aliasActions.length ? 'UPDATE_ALIASES' : 'INDEX_STATE_UPDATE_DONE', + controlState: 'INDEX_STATE_UPDATE_DONE', previousMappings: state.indexMappings, currentIndexMeta, aliases: [], - aliasActions, + aliasActions: [], skipDocumentMigration: true, previousAlgorithm: 'zdt', }; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.mocks.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.mocks.ts index c6a03c7ee6860..14b446463a455 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.mocks.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.mocks.ts @@ -13,6 +13,8 @@ export const generateAdditiveMappingDiffMock = jest.fn(); export const getAliasActionsMock = jest.fn(); export const checkIndexCurrentAlgorithmMock = jest.fn(); +export const getCreationAliasesMock = jest.fn(); + jest.doMock('../../utils', () => { const realModule = jest.requireActual('../../utils'); return { @@ -23,6 +25,7 @@ jest.doMock('../../utils', () => { generateAdditiveMappingDiff: generateAdditiveMappingDiffMock, getAliasActions: getAliasActionsMock, checkIndexCurrentAlgorithm: checkIndexCurrentAlgorithmMock, + getCreationAliases: getCreationAliasesMock, }; }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts index bdaabfcc863cd..892ac57c7e79c 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.test.ts @@ -14,6 +14,7 @@ import { getAliasActionsMock, checkIndexCurrentAlgorithmMock, getAliasesMock, + getCreationAliasesMock, } from './init.test.mocks'; import * as Either from 'fp-ts/lib/Either'; import { FetchIndexResponse } from '../../../actions'; @@ -57,6 +58,7 @@ describe('Stage: init', () => { checkIndexCurrentAlgorithmMock.mockReset().mockReturnValue('zdt'); getAliasesMock.mockReset().mockReturnValue(Either.right({})); buildIndexMappingsMock.mockReset().mockReturnValue({}); + getCreationAliasesMock.mockReset().mockReturnValue([]); context = createContextMock({ indexPrefix: '.kibana', types: ['foo', 'bar'] }); context.typeRegistry.registerType({ @@ -310,6 +312,20 @@ describe('Stage: init', () => { }); }); + it('calls getCreationAliases with the correct parameters', () => { + const state = createState(); + const fetchIndexResponse = createResponse(); + const res: StateActionResponse<'INIT'> = Either.right(fetchIndexResponse); + + init(state, res, context); + + expect(getCreationAliasesMock).toHaveBeenCalledTimes(1); + expect(getCreationAliasesMock).toHaveBeenCalledWith({ + indexPrefix: context.indexPrefix, + kibanaVersion: context.kibanaVersion, + }); + }); + it('INIT -> CREATE_TARGET_INDEX', () => { const state = createState(); const fetchIndexResponse = createResponse(); @@ -318,6 +334,9 @@ describe('Stage: init', () => { const mockMappings = { properties: { someMappings: 'string' } }; buildIndexMappingsMock.mockReturnValue(mockMappings); + const creationAliases = ['.foo', '.bar']; + getCreationAliasesMock.mockReturnValue(creationAliases); + const newState = init(state, res, context); expect(newState).toEqual( @@ -325,6 +344,7 @@ describe('Stage: init', () => { controlState: 'CREATE_TARGET_INDEX', currentIndex: '.kibana_1', indexMappings: mockMappings, + creationAliases, }) ); }); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.ts index 7524e7b2d9f09..7405515c88b56 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/model/stages/init.ts @@ -18,6 +18,7 @@ import { checkVersionCompatibility, buildIndexMappings, getAliasActions, + getCreationAliases, generateAdditiveMappingDiff, checkIndexCurrentAlgorithm, removePropertiesFromV2, @@ -73,6 +74,10 @@ export const init: ModelStage< controlState: 'CREATE_TARGET_INDEX', currentIndex: `${context.indexPrefix}_1`, indexMappings: buildIndexMappings({ types }), + creationAliases: getCreationAliases({ + indexPrefix: context.indexPrefix, + kibanaVersion: context.kibanaVersion, + }), }; } diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.test.ts index 91a37ee7e9919..d9de1c8ce5b6a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.test.ts @@ -16,6 +16,7 @@ import { nextActionMap, type ActionMap } from './next'; import { createContextMock, type MockedMigratorContext, + createPostInitState, createPostDocInitState, } from './test_helpers'; import type { @@ -23,6 +24,7 @@ import type { UpdateMappingModelVersionState, UpdateDocumentModelVersionsState, UpdateIndexMappingsState, + CreateTargetIndexState, } from './state'; describe('actions', () => { @@ -73,6 +75,33 @@ describe('actions', () => { }); }); + describe('CREATE_TARGET_INDEX', () => { + it('calls createIndex with the correct parameters', () => { + const state: CreateTargetIndexState = { + ...createPostInitState(), + controlState: 'CREATE_TARGET_INDEX', + currentIndex: '.kibana_1', + indexMappings: { + properties: { foo: { type: 'keyword' } }, + }, + creationAliases: ['.kibana', '.kibana_foo'], + }; + + const action = actionMap.CREATE_TARGET_INDEX; + + action(state); + + expect(ActionMocks.createIndex).toHaveBeenCalledTimes(1); + expect(ActionMocks.createIndex).toHaveBeenCalledWith({ + client: context.elasticsearchClient, + indexName: state.currentIndex, + aliases: state.creationAliases, + mappings: state.indexMappings, + esCapabilities: context.esCapabilities, + }); + }); + }); + describe('UPDATE_MAPPING_MODEL_VERSIONS', () => { it('calls setMetaMappingMigrationComplete with the correct parameters', () => { const state: UpdateMappingModelVersionState = { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts index 66df07ac37309..3073dd66e73f1 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/next.ts @@ -65,6 +65,7 @@ export const nextActionMap = (context: MigratorContext) => { Actions.createIndex({ client, indexName: state.currentIndex, + aliases: state.creationAliases, mappings: state.indexMappings, esCapabilities: context.esCapabilities, }), diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/types.ts index 7a1768745982e..23748f3d5e14b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/types.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/state/types.ts @@ -98,6 +98,7 @@ export interface CreateTargetIndexState extends BaseState { readonly controlState: 'CREATE_TARGET_INDEX'; readonly currentIndex: string; readonly indexMappings: IndexMapping; + readonly creationAliases: string[]; } export interface UpdateIndexMappingsState extends PostInitState { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/get_creation_aliases.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/get_creation_aliases.test.ts new file mode 100644 index 0000000000000..3318ea332b9bb --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/get_creation_aliases.test.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 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 { getCreationAliases } from './get_creation_aliases'; + +describe('getCreationAliases', () => { + it('returns the correct list of alias', () => { + const aliases = getCreationAliases({ indexPrefix: '.kibana', kibanaVersion: '8.13.0' }); + expect(aliases).toEqual(['.kibana', '.kibana_8.13.0']); + }); + + it('returns the correct version alias', () => { + const aliases = getCreationAliases({ indexPrefix: '.kibana', kibanaVersion: '8.17.2' }); + expect(aliases).toEqual(['.kibana', '.kibana_8.17.2']); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/get_creation_aliases.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/get_creation_aliases.ts new file mode 100644 index 0000000000000..f2171c233aa6d --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/get_creation_aliases.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +interface GetAliasActionOpts { + indexPrefix: string; + kibanaVersion: string; +} + +/** + * Build the list of alias actions to perform, depending on the current state of the cluster. + */ +export const getCreationAliases = ({ + indexPrefix, + kibanaVersion, +}: GetAliasActionOpts): string[] => { + const globalAlias = indexPrefix; + const versionAlias = `${indexPrefix}_${kibanaVersion}`; + return [globalAlias, versionAlias]; +}; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/index.ts index b1e68c6947823..37d6b56dfc597 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/zdt/utils/index.ts @@ -24,3 +24,4 @@ export { checkIndexCurrentAlgorithm, type CheckCurrentAlgorithmResult, } from './check_index_algorithm'; +export { getCreationAliases } from './get_creation_aliases'; diff --git a/packages/kbn-es/src/cli_commands/serverless.ts b/packages/kbn-es/src/cli_commands/serverless.ts index f74eeb033e9a9..6b0fe4acd5be7 100644 --- a/packages/kbn-es/src/cli_commands/serverless.ts +++ b/packages/kbn-es/src/cli_commands/serverless.ts @@ -38,7 +38,8 @@ export const serverless: Command = { --tag Image tag of ES serverless to run from ${ES_SERVERLESS_REPO_ELASTICSEARCH} --image Full path of ES serverless image to run, has precedence over tag. [default: ${ES_SERVERLESS_DEFAULT_IMAGE}] --background Start ES serverless without attaching to the first node's logs - --basePath Path to the directory where the ES cluster will store data + --basePath Full path where the ES cluster will store data [default: /.es] + --dataPath Directory in basePath where the ES cluster will store data [default: stateless] --clean Remove existing file system object store before running --kill Kill running ES serverless nodes if detected on startup --host Publish ES docker container on additional host IP @@ -87,12 +88,22 @@ export const serverless: Command = { esArgs: 'E', files: 'F', projectType: 'project-type', + dataPath: 'data-path', }, - string: ['projectType', 'tag', 'image', 'basePath', 'resources', 'host', 'kibanaUrl'], + string: [ + 'projectType', + 'tag', + 'image', + 'basePath', + 'resources', + 'host', + 'kibanaUrl', + 'dataPath', + ], boolean: ['clean', 'ssl', 'kill', 'background', 'skipTeardown', 'waitForReady'], - default: { ...defaults, kibanaUrl: 'https://localhost:5601/' }, + default: { ...defaults, kibanaUrl: 'https://localhost:5601/', dataPath: 'stateless' }, }) as unknown as ServerlessOptions; if (!options.projectType) { diff --git a/packages/kbn-es/src/utils/docker.test.ts b/packages/kbn-es/src/utils/docker.test.ts index 33b02d7df0d8e..ec706a64a96ac 100644 --- a/packages/kbn-es/src/utils/docker.test.ts +++ b/packages/kbn-es/src/utils/docker.test.ts @@ -103,12 +103,13 @@ const serverlessResources = SERVERLESS_RESOURCES_PATHS.reduce((acc, pa }, []); const volumeCmdTest = async (volumeCmd: string[]) => { - expect(volumeCmd).toHaveLength(20); + expect(volumeCmd).toHaveLength(22); expect(volumeCmd).toEqual( expect.arrayContaining([ ...getESp12Volume(), ...serverlessResources, `${baseEsPath}:/objectstore:z`, + `stateless.object_store.bucket=${serverlessDir}`, `${SERVERLESS_SECRETS_PATH}:${SERVERLESS_CONFIG_PATH}secrets/secrets.json:z`, `${SERVERLESS_JWKS_PATH}:${SERVERLESS_CONFIG_PATH}secrets/jwks.json:z`, ]) @@ -564,7 +565,7 @@ describe('setupServerlessVolumes()', () => { const pathsNotIncludedInCmd = requiredPaths.filter( (path) => !volumeCmd.some((cmd) => cmd.includes(path)) ); - expect(volumeCmd).toHaveLength(22); + expect(volumeCmd).toHaveLength(24); expect(pathsNotIncludedInCmd).toEqual([]); }); @@ -598,6 +599,25 @@ describe('setupServerlessVolumes()', () => { 'Valid resources: operator_users.yml | role_mapping.yml | service_tokens | users | users_roles | roles.yml' ); }); + + test('should override data path when passed', async () => { + const dataPath = 'stateless-cluster-ftr'; + + mockFs({ + [baseEsPath]: {}, + }); + + const volumeCmd = await setupServerlessVolumes(log, { + projectType, + basePath: baseEsPath, + dataPath, + }); + + expect(volumeCmd).toEqual( + expect.arrayContaining([`stateless.object_store.bucket=${dataPath}`]) + ); + await expect(Fsp.access(`${baseEsPath}/${dataPath}`)).resolves.not.toThrow(); + }); }); describe('runServerlessEsNode()', () => { diff --git a/packages/kbn-es/src/utils/docker.ts b/packages/kbn-es/src/utils/docker.ts index c263ac686fc84..b0795a70f7d72 100644 --- a/packages/kbn-es/src/utils/docker.ts +++ b/packages/kbn-es/src/utils/docker.ts @@ -77,8 +77,10 @@ export interface ServerlessOptions extends EsClusterExecOptions, BaseOptions { projectType: ServerlessProjectType; /** Clean (or delete) all data created by the ES cluster after it is stopped */ clean?: boolean; - /** Path to the directory where the ES cluster will store data */ + /** Full path where the ES cluster will store data */ basePath: string; + /** Directory in basePath where the ES cluster will store data */ + dataPath?: string; /** If this process exits, leave the ES cluster running in the background */ skipTeardown?: boolean; /** Start the ES cluster in the background instead of remaining attached: useful for running tests */ @@ -167,9 +169,6 @@ const SHARED_SERVERLESS_PARAMS = [ '--env', 'stateless.object_store.type=fs', - - '--env', - 'stateless.object_store.bucket=stateless', ]; // only allow certain ES args to be overwrote by options @@ -546,8 +545,17 @@ export function getDockerFileMountPath(hostPath: string) { * Setup local volumes for Serverless ES */ export async function setupServerlessVolumes(log: ToolingLog, options: ServerlessOptions) { - const { basePath, clean, ssl, kibanaUrl, files, resources, projectType } = options; - const objectStorePath = resolve(basePath, 'stateless'); + const { + basePath, + clean, + ssl, + kibanaUrl, + files, + resources, + projectType, + dataPath = 'stateless', + } = options; + const objectStorePath = resolve(basePath, dataPath); log.info(chalk.bold(`Checking for local serverless ES object store at ${objectStorePath}`)); log.indent(4); @@ -579,7 +587,12 @@ export async function setupServerlessVolumes(log: ToolingLog, options: Serverles log.indent(-4); - const volumeCmds = ['--volume', `${basePath}:/objectstore:z`]; + const volumeCmds = [ + '--volume', + `${basePath}:/objectstore:z`, + '--env', + `stateless.object_store.bucket=${dataPath}`, + ]; if (files) { const _files = typeof files === 'string' ? [files] : files; diff --git a/packages/kbn-esql-ast/src/ast_walker.ts b/packages/kbn-esql-ast/src/ast_walker.ts index 05393d7b07c16..740986ec3edd6 100644 --- a/packages/kbn-esql-ast/src/ast_walker.ts +++ b/packages/kbn-esql-ast/src/ast_walker.ts @@ -294,7 +294,7 @@ function getBooleanValue(ctx: BooleanLiteralContext | BooleanValueContext) { function getConstant(ctx: ConstantContext | undefined): ESQLAstItem | undefined { if (ctx instanceof NullLiteralContext) { - return createLiteral('string', ctx.NULL()); + return createLiteral('null', ctx.NULL()); } if (ctx instanceof QualifiedIntegerLiteralContext) { // despite the generic name, this is a date unit constant: diff --git a/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts b/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts index 668a2cd20407b..23da9e1684cf4 100644 --- a/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts +++ b/packages/kbn-esql-validation-autocomplete/src/definitions/builtin.ts @@ -337,6 +337,27 @@ export const builtinFunctions: FunctionDefinition[] = [ ], returnType: 'boolean', }, + { + params: [ + { name: 'left', type: 'null' }, + { name: 'right', type: 'boolean' }, + ], + returnType: 'boolean', + }, + { + params: [ + { name: 'left', type: 'boolean' }, + { name: 'right', type: 'null' }, + ], + returnType: 'boolean', + }, + { + params: [ + { name: 'left', type: 'null' }, + { name: 'right', type: 'null' }, + ], + returnType: 'boolean', + }, ], })), { diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json index 0db7e695f8a62..d6919bb97551f 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json +++ b/packages/kbn-esql-validation-autocomplete/src/validation/esql_validation_meta_tests.json @@ -652,6 +652,46 @@ ], "warning": [] }, + { + "query": "row bool_var = true and false", + "error": [], + "warning": [] + }, + { + "query": "row bool_var = true and null", + "error": [], + "warning": [] + }, + { + "query": "row bool_var = null and false", + "error": [], + "warning": [] + }, + { + "query": "row bool_var = null and null", + "error": [], + "warning": [] + }, + { + "query": "row bool_var = true or false", + "error": [], + "warning": [] + }, + { + "query": "row bool_var = true or null", + "error": [], + "warning": [] + }, + { + "query": "row bool_var = null or false", + "error": [], + "warning": [] + }, + { + "query": "row bool_var = null or null", + "error": [], + "warning": [] + }, { "query": "row var = abs(5)", "error": [], @@ -5118,6 +5158,11 @@ "error": [], "warning": [] }, + { + "query": "from a_index | where stringField == \"a\" or null", + "error": [], + "warning": [] + }, { "query": "from a_index | where avg(numberField)", "error": [ @@ -10587,6 +10632,11 @@ "error": [], "warning": [] }, + { + "query": "from a_index | stats count(stringField == \"a\" or null)", + "error": [], + "warning": [] + }, { "query": "from a_index | stats count(`numberField`) | keep `count(``numberField``)` ", "error": [], diff --git a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts index 1d934731b9725..498d5f264a8f6 100644 --- a/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts +++ b/packages/kbn-esql-validation-autocomplete/src/validation/validation.test.ts @@ -508,6 +508,15 @@ describe('validation logic', () => { 'Argument of [not_in] must be [number[]], found value [(1, 2, 3, "a")] type [(number, number, number, string)]', ]); + // test that "and" and "or" accept null... not sure if this is the best place or not... + for (const op of ['and', 'or']) { + for (const firstParam of ['true', 'null']) { + for (const secondParam of ['false', 'null']) { + testErrorsAndWarnings(`row bool_var = ${firstParam} ${op} ${secondParam}`, []); + } + } + } + function tweakSignatureForRowCommand(signature: string) { /** * row has no access to any field, so replace it with literal @@ -1098,6 +1107,9 @@ describe('validation logic', () => { testErrorsAndWarnings(`from a_index | where ${camelCase(field)}Field Is nOt NuLL`, []); } + // this is a scenario that was failing because "or" didn't accept "null" + testErrorsAndWarnings('from a_index | where stringField == "a" or null', []); + for (const { name, alias, @@ -1742,6 +1754,9 @@ describe('validation logic', () => { ]); testErrorsAndWarnings('from a_index | stats count(`numberField`)', []); + // this is a scenario that was failing because "or" didn't accept "null" + testErrorsAndWarnings('from a_index | stats count(stringField == "a" or null)', []); + for (const subCommand of ['keep', 'drop', 'eval']) { testErrorsAndWarnings( `from a_index | stats count(\`numberField\`) | ${subCommand} \`count(\`\`numberField\`\`)\` `, diff --git a/packages/kbn-test/src/es/test_es_cluster.ts b/packages/kbn-test/src/es/test_es_cluster.ts index 3c39a2e2a1cc2..0f94a76d87f71 100644 --- a/packages/kbn-test/src/es/test_es_cluster.ts +++ b/packages/kbn-test/src/es/test_es_cluster.ts @@ -73,7 +73,7 @@ export interface CreateTestEsClusterOptions { esFrom?: string; esServerlessOptions?: Pick< ServerlessOptions, - 'image' | 'tag' | 'resources' | 'host' | 'kibanaUrl' | 'projectType' + 'image' | 'tag' | 'resources' | 'host' | 'kibanaUrl' | 'projectType' | 'dataPath' >; esJavaOpts?: string; /** @@ -250,6 +250,7 @@ export function createTestEsCluster< await firstNode.runServerless({ basePath, esArgs: customEsArgs, + dataPath: `stateless-${clusterName}`, ...esServerlessOptions, port, clean: true, diff --git a/src/core/server/integration_tests/saved_objects/migrations/zdt_1/create_index.test.ts b/src/core/server/integration_tests/saved_objects/migrations/zdt_1/create_index.test.ts index 784bd6c6e2699..f0b049b8307db 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/zdt_1/create_index.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/zdt_1/create_index.test.ts @@ -83,10 +83,14 @@ describe('ZDT upgrades - running on a fresh cluster', () => { const records = await parseLogFile(logFilePath); - expect(records).toContainLogEntry('INIT -> CREATE_TARGET_INDEX'); - expect(records).toContainLogEntry('CREATE_TARGET_INDEX -> UPDATE_ALIASES'); - expect(records).toContainLogEntry('UPDATE_ALIASES -> INDEX_STATE_UPDATE_DONE'); - expect(records).toContainLogEntry('INDEX_STATE_UPDATE_DONE -> DONE'); - expect(records).toContainLogEntry('Migration completed'); + expect(records).toContainLogEntries( + [ + 'INIT -> CREATE_TARGET_INDEX', + 'CREATE_TARGET_INDEX -> INDEX_STATE_UPDATE_DONE', + 'INDEX_STATE_UPDATE_DONE -> DONE', + 'Migration completed', + ], + { ordered: true } + ); }); }); diff --git a/src/core/server/integration_tests/saved_objects/migrations/zdt_v2_compat/create_index.test.ts b/src/core/server/integration_tests/saved_objects/migrations/zdt_v2_compat/create_index.test.ts index 6c95b9696538e..6f1b8257ddb26 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/zdt_v2_compat/create_index.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/zdt_v2_compat/create_index.test.ts @@ -83,10 +83,14 @@ describe('ZDT with v2 compat - running on a fresh cluster', () => { const records = await parseLogFile(logFilePath); - expect(records).toContainLogEntry('INIT -> CREATE_TARGET_INDEX'); - expect(records).toContainLogEntry('CREATE_TARGET_INDEX -> UPDATE_ALIASES'); - expect(records).toContainLogEntry('UPDATE_ALIASES -> INDEX_STATE_UPDATE_DONE'); - expect(records).toContainLogEntry('INDEX_STATE_UPDATE_DONE -> DONE'); - expect(records).toContainLogEntry('Migration completed'); + expect(records).toContainLogEntries( + [ + 'INIT -> CREATE_TARGET_INDEX', + 'CREATE_TARGET_INDEX -> INDEX_STATE_UPDATE_DONE', + 'INDEX_STATE_UPDATE_DONE -> DONE', + 'Migration completed', + ], + { ordered: true } + ); }); }); diff --git a/versions.json b/versions.json index 9dc74f92ba87a..1f8467c72b636 100644 --- a/versions.json +++ b/versions.json @@ -8,7 +8,7 @@ "currentMinor": true }, { - "version": "8.13.1", + "version": "8.13.2", "branch": "8.13", "currentMajor": true, "previousMinor": true diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 6909e7a7d44f1..77f2e51b80393 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -1489,7 +1489,9 @@ export class Embeddable const input = this.getInput(); // if at least one indexPattern is time based, then the Lens embeddable requires the timeRange prop + // this is necessary for the dataview embeddable but not the ES|QL one if ( + !Boolean(this.isTextBasedLanguage()) && input.timeRange == null && indexPatterns.some((indexPattern) => indexPattern.isTimeBased()) ) { diff --git a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx index 416ec8347847a..048c8dbe2b5eb 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx @@ -22,6 +22,7 @@ import { EuiTextAlign, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useTimefilter } from '@kbn/ml-date-picker'; import { isFullLicense } from '../license'; @@ -182,6 +183,12 @@ export const DatavisualizerSelector: FC = () => { /> } tooltipPosition={'right'} + aria-label={i18n.translate( + 'xpack.ml.datavisualizer.selector.technicalPreviewBadge.ariaLabel', + { + defaultMessage: 'ES|QL is in technical preview.', + } + )} /> diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.test.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.test.tsx index ffa4f08e2d144..dbc084ee739bc 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.test.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.test.tsx @@ -41,6 +41,7 @@ const ruleType: RuleTypeModel = { requiresAppContext: false, validate: validationMethod, ruleParamsExpression: () => , + alertDetailsAppSection: () => , }; const ruleTypeRegistry = ruleTypeRegistryMock.create(); @@ -135,7 +136,27 @@ describe('Alert details', () => { expect(alertDetails.queryByTestId('alertDetails')).toBeTruthy(); expect(alertDetails.queryByTestId('alertDetailsError')).toBeFalsy(); expect(alertDetails.queryByTestId('alertDetailsPageTitle')).toBeTruthy(); + expect(alertDetails.queryByTestId('alertDetailsTabbedContent')).toBeTruthy(); expect(alertDetails.queryByTestId('alert-summary-container')).toBeTruthy(); + expect(alertDetails.queryByTestId('overviewTab')).toBeTruthy(); + expect(alertDetails.queryByTestId('metadataTab')).toBeTruthy(); + }); + + it('should show Metadata tab', async () => { + useFetchAlertDetailMock.mockReturnValue([false, alertDetail]); + + const alertDetails = renderComponent(); + + await waitFor(() => expect(alertDetails.queryByTestId('centerJustifiedSpinner')).toBeFalsy()); + + expect(alertDetails.queryByTestId('alertDetailsTabbedContent')?.textContent).toContain( + 'Metadata' + ); + alertDetails.getByText('Metadata').click(); + expect(alertDetails.queryByTestId('metadataTabPanel')).toBeTruthy(); + expect(alertDetails.queryByTestId('metadataTabPanel')?.textContent).toContain( + 'kibana.alert.status' + ); }); it('should show error loading the alert details', async () => { diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx index 6c15585dcf9f5..cfd9f277b305e 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/alert_details.tsx @@ -8,7 +8,14 @@ import React, { useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { useParams } from 'react-router-dom'; -import { EuiEmptyPrompt, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { + EuiEmptyPrompt, + EuiPanel, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentTab, + useEuiTheme, +} from '@elastic/eui'; import { AlertStatus, ALERT_RULE_CATEGORY, @@ -21,6 +28,8 @@ import { RuleTypeModel } from '@kbn/triggers-actions-ui-plugin/public'; import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public'; import dedent from 'dedent'; +import { AlertFieldsTable } from '@kbn/alerts-ui-shared'; +import { css } from '@emotion/react'; import { useKibana } from '../../utils/kibana_react'; import { useFetchRule } from '../../hooks/use_fetch_rule'; import { usePluginContext } from '../../hooks/use_plugin_context'; @@ -74,6 +83,7 @@ export function AlertDetails() { }); const [summaryFields, setSummaryFields] = useState(); const [alertStatus, setAlertStatus] = useState(); + const { euiTheme } = useEuiTheme(); useEffect(() => { if (!alertDetail) { @@ -168,6 +178,50 @@ export function AlertDetails() { const AlertDetailsAppSection = ruleTypeModel ? ruleTypeModel.alertDetailsAppSection : null; const timeZone = getTimeZone(uiSettings); + const OVERVIEW_TAB_ID = 'overview'; + const METADATA_TAB_ID = 'metadata'; + + const overviewTab = ( + <> + + + {AlertDetailsAppSection && rule && alertDetail?.formatted && ( + + )} + + ); + + const metadataTab = alertDetail?.raw && ( + + + + ); + + const tabs: EuiTabbedContentTab[] = [ + { + id: OVERVIEW_TAB_ID, + name: i18n.translate('xpack.observability.alertDetails.tab.overviewLabel', { + defaultMessage: 'Overview', + }), + 'data-test-subj': 'overviewTab', + content: overviewTab, + }, + { + id: METADATA_TAB_ID, + name: i18n.translate('xpack.observability.alertDetails.tab.metadataLabel', { + defaultMessage: 'Metadata', + }), + 'data-test-subj': 'metadataTab', + content: metadataTab, + }, + ]; + return ( , ], - bottomBorder: true, + bottomBorder: false, + }} + pageSectionProps={{ + paddingSize: 'none', + css: css` + padding: 0 ${euiTheme.size.l} ${euiTheme.size.l} ${euiTheme.size.l}; + `, }} data-test-subj="alertDetails" > - - - {AlertDetailsAppSection && rule && alertDetail?.formatted && ( - - )} + ); } diff --git a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/alert_summary.tsx b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/alert_summary.tsx index ee750a86e5b4a..20d0fb36f9f8a 100644 --- a/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/alert_summary.tsx +++ b/x-pack/plugins/observability_solution/observability/public/pages/alert_details/components/alert_summary.tsx @@ -6,7 +6,7 @@ */ import React, { ReactNode } from 'react'; -import { EuiFlexItem, EuiFlexGroup, EuiText } from '@elastic/eui'; +import { EuiFlexItem, EuiFlexGroup, EuiText, EuiSpacer } from '@elastic/eui'; export interface AlertSummaryField { label: ReactNode | string; @@ -18,15 +18,22 @@ interface AlertSummaryProps { export function AlertSummary({ alertSummaryFields }: AlertSummaryProps) { return ( - - {alertSummaryFields?.map((field, idx) => { - return ( - - {field.label} - {field.value} - - ); - })} - +
+ {alertSummaryFields && alertSummaryFields.length > 0 && ( + <> + + {alertSummaryFields.map((field, idx) => { + return ( + + {field.label} + {field.value} + + ); + })} + + + + )} +
); } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/query_tab_unified_components.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/query_tab_unified_components.test.tsx index 05aae984f0357..c87c8dfdddfe4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/query_tab_unified_components.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/query_tab_unified_components.test.tsx @@ -134,7 +134,8 @@ const useSourcererDataViewMocked = jest.fn().mockReturnValue({ const { storage: storageMock } = createSecuritySolutionStorageMock(); -describe('query tab with unified timeline', () => { +// Failing: See https://github.com/elastic/kibana/issues/179831 +describe.skip('query tab with unified timeline', () => { const kibanaServiceMock: StartServices = { ...createStartServicesMock(), storage: storageMock, @@ -475,7 +476,8 @@ describe('query tab with unified timeline', () => { ); }); - describe('left controls', () => { + // FLAKY: https://github.com/elastic/kibana/issues/179845 + describe.skip('left controls', () => { it( 'should clear all sorting', async () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.test.tsx index 4beae12e85a95..ba909390aa3fb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/unified_components/data_table/index.test.tsx @@ -91,7 +91,8 @@ describe('unified data table', () => { SPECIAL_TEST_TIMEOUT ); - describe('custom cell rendering based on data Type', () => { + // FLAKY: https://github.com/elastic/kibana/issues/179843 + describe.skip('custom cell rendering based on data Type', () => { it( 'should render source.ip as link', async () => { diff --git a/x-pack/test/accessibility/apps/group2/ml.ts b/x-pack/test/accessibility/apps/group2/ml.ts index add5811a3ab54..af9664b5e258a 100644 --- a/x-pack/test/accessibility/apps/group2/ml.ts +++ b/x-pack/test/accessibility/apps/group2/ml.ts @@ -48,8 +48,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/172598 - describe.skip('with data loaded', function () { + describe('with data loaded', function () { const dfaOutlierResultsJobId = 'iph_outlier_a11y'; const ecIndexName = 'ft_module_sample_ecommerce'; const ihpIndexName = 'ft_ihp_outlier'; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index 0e118cf88e4b9..db290065bfe24 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -403,7 +403,8 @@ export default function ({ getService }: FtrProviderContext) { }); }); - describe('get metadata transforms', () => { + // FLAKY: https://github.com/elastic/kibana/issues/175791 + describe.skip('get metadata transforms', () => { const testRegex = /(endpoint|logs-endpoint)\.metadata_(united|current)-default-*/; let currentTransformName = metadataTransformPrefix; let unitedTransformName = METADATA_UNITED_TRANSFORM;