Skip to content

Commit

Permalink
Merge branch 'main' into fix/files-server-integration-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sebelga authored Aug 29, 2023
2 parents e52102d + bc72af1 commit 86880d9
Show file tree
Hide file tree
Showing 75 changed files with 6,069 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,12 @@ packages/kbn-yarn-lock-validator @elastic/kibana-operations
/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js @elastic/kibana-data-discovery
/x-pack/test/stack_functional_integration/apps/management/_index_pattern_create.js @elastic/kibana-data-discovery
/x-pack/test/upgrade/apps/discover @elastic/kibana-data-discovery
/x-pack/test_serverless/api_integration/test_suites/common/data_views @elastic/kibana-data-discovery
/x-pack/test_serverless/api_integration/test_suites/common/data_view_field_editor @elastic/kibana-data-discovery
/x-pack/test_serverless/api_integration/test_suites/common/kql_telemetry @elastic/kibana-data-discovery
/x-pack/test_serverless/api_integration/test_suites/common/scripts_tests @elastic/kibana-data-discovery
/x-pack/test_serverless/api_integration/test_suites/common/search_oss @elastic/kibana-data-discovery
/x-pack/test_serverless/api_integration/test_suites/common/search_xpack @elastic/kibana-data-discovery

# Visualizations
/src/plugins/visualize/ @elastic/kibana-visualizations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ export class ContentStream extends Duplex {
* of holding, at most, 2 full chunks in memory.
*/
private indexRequestBuffer: undefined | IndexRequestParams;

private async writeChunk(data: Buffer) {
const chunkId = this.getChunkId(this.chunksWritten);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,19 @@ export class ElasticsearchBlobStorageClient implements BlobStorageClient {
}

public async download({ id, size }: { id: string; size?: number }): Promise<Readable> {
// The refresh interval is set to 10s. To avoid throwing an error if the user tries to download a file
// right after uploading it, we refresh the index before downloading the file.
await this.esClient.indices.refresh({ index: this.index });

return this.getReadableContentStream(id, size);
}

public async delete(id: string): Promise<void> {
try {
// The refresh interval is set to 10s. To avoid throwing an error if the user tries to delete a file
// right after uploading it, we refresh the index before deleting the file.
await this.esClient.indices.refresh({ index: this.index });

const dest = getWritableContentStream({
id,
client: this.esClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('Elasticsearch blob storage', () => {
let esBlobStorage: ElasticsearchBlobStorageClient;
let esClient: ElasticsearchClient;
let esGetSpy: jest.SpyInstance;
let esRefreshIndexSpy: jest.SpyInstance;

beforeAll(async () => {
ElasticsearchBlobStorageClient.configureConcurrentUpload(Infinity);
Expand All @@ -48,6 +49,7 @@ describe('Elasticsearch blob storage', () => {
beforeEach(() => {
esBlobStorage = createEsBlobStorage();
esGetSpy = jest.spyOn(esClient, 'get');
esRefreshIndexSpy = jest.spyOn(esClient.indices, 'refresh');
});

afterEach(async () => {
Expand Down Expand Up @@ -105,7 +107,9 @@ describe('Elasticsearch blob storage', () => {
esBlobStorage = createEsBlobStorage({ chunkSize: '1024B' });
const { id } = await esBlobStorage.upload(Readable.from([fileString]));
expect(await getAllDocCount()).toMatchObject({ count: 37 });
esRefreshIndexSpy.mockReset();
const rs = await esBlobStorage.download({ id });
expect(esRefreshIndexSpy).toHaveBeenCalled(); // Make sure we refresh the index before downloading the chunks
const chunks: string[] = [];
for await (const chunk of rs) {
chunks.push(chunk);
Expand Down Expand Up @@ -137,7 +141,9 @@ describe('Elasticsearch blob storage', () => {
const { id } = await esBlobStorage.upload(Readable.from([fileString]));
const { id: id2 } = await esBlobStorage.upload(Readable.from([fileString2]));
expect(await getAllDocCount()).toMatchObject({ count: 10 });
esRefreshIndexSpy.mockReset();
await esBlobStorage.delete(id);
expect(esRefreshIndexSpy).toHaveBeenCalled(); // Make sure we refresh the index before deleting the chunks
expect(await getAllDocCount()).toMatchObject({ count: 2 });
// Now we check that the other file is still intact
const rs = await esBlobStorage.download({ id: id2 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,32 @@ export class WebElementWrapper {
}

/**
* Scroll the element into view, avoiding the fixed header if necessary
* Scroll the element into view
*
* @param {ScrollIntoViewOptions} scrollIntoViewOptions
* @return {Promise<void>}
*/
public scrollIntoView(scrollIntoViewOptions?: ScrollIntoViewOptions) {
return this.driver.executeScript<void>(
(target: HTMLElement, options: ScrollIntoViewOptions) => target.scrollIntoView(options),
this._webElement,
scrollIntoViewOptions
);
}

/**
* Scroll the element into view if it is not already, avoiding the fixed header if necessary
* This method is a variation of the scrollIntoView method, where we only scroll into an element
* if it is not part of the "scrollable view".
* This implies a specific behavior, since the "scrollable view" of the view is identified by
* the `document.scrollingElement`, which always results to the html or body tag.
*
* Use cases:
* - An element (a section, a footer) is not visible in the whole page and we need to scroll into it.
* - An element is covered by the fixed header and we need to scroll into it ensuring is not covered.
*
* In case you have a scrollable list smaller that the size of the HTML document and you need
* to scroll into an element of that list, prefer using the `.scrollIntoView` method.
*
* @nonstandard
* @return {Promise<void>}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import expect from '@kbn/expect';

import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { getErrorCodeFromErrorReason } from '@kbn/data-view-field-editor-plugin/public/lib/runtime_field_validation';
import {
FIELD_PREVIEW_PATH,
INITIAL_REST_VERSION,
} from '@kbn/data-view-field-editor-plugin/common/constants';
import type { FtrProviderContext } from '../../../ftr_provider_context';

const INDEX_NAME = 'api-integration-test-field-preview';

export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const es = getService('es');
const svlCommonApi = getService('svlCommonApi');

const document = { foo: 1, bar: 'hello' };

const createIndex = async () => {
await es.indices.create({
index: INDEX_NAME,
body: {
mappings: {
properties: {
foo: {
type: 'integer',
},
bar: {
type: 'keyword',
},
},
},
},
});
};

const deleteIndex = async () => {
await es.indices.delete({
index: INDEX_NAME,
});
};

describe('Field preview', function () {
before(async () => await createIndex());
after(async () => await deleteIndex());

describe('should return the script value', () => {
const tests = [
{
context: 'keyword_field',
script: {
source: 'emit("test")',
},
expected: 'test',
},
{
context: 'long_field',
script: {
source: 'emit(doc["foo"].value + 1)',
},
expected: 2,
},
{
context: 'keyword_field',
script: {
source: 'emit(doc["bar"].value + " world")',
},
expected: 'hello world',
},
];

tests.forEach((test) => {
it(`> ${test.context}`, async () => {
const payload = {
script: test.script,
document,
context: test.context,
index: INDEX_NAME,
};

const { body: response } = await supertest
.post(FIELD_PREVIEW_PATH)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
// TODO: API requests in Serverless require internal request headers
.set(svlCommonApi.getInternalRequestHeader())
.send(payload)
.set('kbn-xsrf', 'xxx')
.expect(200);

expect(response.values).eql([test.expected]);
});
});
});

describe('payload validation', () => {
it('should require a script', async () => {
await supertest
.post(FIELD_PREVIEW_PATH)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
// TODO: API requests in Serverless require internal request headers
.set(svlCommonApi.getInternalRequestHeader())
.send({
context: 'keyword_field',
index: INDEX_NAME,
})
.set('kbn-xsrf', 'xxx')
.expect(400);
});

it('should require a context', async () => {
await supertest
.post(FIELD_PREVIEW_PATH)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
// TODO: API requests in Serverless require internal request headers
.set(svlCommonApi.getInternalRequestHeader())
.send({
script: { source: 'emit("hello")' },
index: INDEX_NAME,
})
.set('kbn-xsrf', 'xxx')
.expect(400);
});

it('should require an index', async () => {
await supertest
.post(FIELD_PREVIEW_PATH)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
// TODO: API requests in Serverless require internal request headers
.set(svlCommonApi.getInternalRequestHeader())
.send({
script: { source: 'emit("hello")' },
context: 'keyword_field',
})
.set('kbn-xsrf', 'xxx')
.expect(400);
});
});

describe('Error messages', () => {
// As ES does not return error codes we will add a test to make sure its error message string
// does not change overtime as we rely on it to extract our own error code.
// If this test fail we'll need to update the "getErrorCodeFromErrorReason()" handler
// TODO: `response.error?.caused_by?.reason` returns
// `class_cast_exception: Cannot cast from [int] to [java.lang.String].`
// in Serverless, which causes `getErrorCodeFromErrorReason` to fail
it.skip('should detect a script casting error', async () => {
const { body: response } = await supertest
.post(FIELD_PREVIEW_PATH)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
// TODO: API requests in Serverless require internal request headers
.set(svlCommonApi.getInternalRequestHeader())
.send({
script: { source: 'emit(123)' }, // We send a long but the type is "keyword"
context: 'keyword_field',
index: INDEX_NAME,
})
.set('kbn-xsrf', 'xxx');

const errorCode = getErrorCodeFromErrorReason(response.error?.caused_by?.reason);

expect(errorCode).be('CAST_ERROR');
});
});
});
}
Original file line number Diff line number Diff line change
@@ -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 { FtrProviderContext } from '../../../ftr_provider_context';

export default function ({ loadTestFile }: FtrProviderContext) {
describe('index pattern field editor', () => {
loadTestFile(require.resolve('./field_preview'));
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 {
DATA_VIEW_PATH_LEGACY,
SERVICE_KEY_LEGACY,
DATA_VIEW_PATH,
SERVICE_KEY,
SERVICE_PATH,
SERVICE_PATH_LEGACY,
} from '@kbn/data-views-plugin/server';

const legacyConfig = {
name: 'legacy index pattern api',
path: DATA_VIEW_PATH_LEGACY,
basePath: SERVICE_PATH_LEGACY,
serviceKey: SERVICE_KEY_LEGACY,
};

export const dataViewConfig = {
name: 'data view api',
path: DATA_VIEW_PATH,
basePath: SERVICE_PATH,
serviceKey: SERVICE_KEY,
};

export const configArray = [legacyConfig, dataViewConfig];
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 { FtrProviderContext } from '../../../../../ftr_provider_context';

export default function ({ loadTestFile }: FtrProviderContext) {
describe('create_index_pattern', () => {
loadTestFile(require.resolve('./validation'));
loadTestFile(require.resolve('./main'));
});
}
Loading

0 comments on commit 86880d9

Please sign in to comment.