Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add migration section for the new ES client #71604

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 260 additions & 1 deletion src/core/MIGRATION_EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ APIs to their New Platform equivalents.
- [Changes in structure compared to legacy](#changes-in-structure-compared-to-legacy)
- [Remarks](#remarks)
- [UiSettings](#uisettings)
- [Elasticsearch client](#elasticsearch-client)
- [Client API Changes](#client-api-changes)
- [Accessing the client from a route handler](#accessing-the-client-from-a-route-handler)
- [Creating a custom client](#creating-a-custom-client)

## Configuration

Expand Down Expand Up @@ -1003,4 +1007,259 @@ setup(core: CoreSetup){
},
})
}
```
```

## Elasticsearch client

The new elasticsearch client is a thin wrapper around `@elastic/elasticsearch`'s `Client` class. Even if the API
is quite close to the legacy client Kibana was previously using, there are some subtle changes to take into account
during migration.

[Official documentation](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html)

### Client API Changes

The most significant changes for the consumers are the following:

- internal / current user client accessors has been renamed and are now properties instead of functions
- `callAsInternalUser('ping')` -> `asInternalUser.ping()`
- `callAsCurrentUser('ping')` -> `asCurrentUser.ping()`

- the API now reflects the `Client`'s instead of leveraging the string-based endpoint names the `LegacyAPICaller` was using

before:

```ts
const body = await client.callAsInternalUser('indices.get', { index: 'id' });
```

after:

```ts
const { body } = await client.asInternalUser.indices.get({ index: 'id' });
```

- calling any ES endpoint now returns the whole response object instead of only the body payload

before:

```ts
const body = await legacyClient.callAsInternalUser('get', { id: 'id' });
```

after:

```ts
const { body } = await client.asInternalUser.get({ id: 'id' });
```

Note that more information from the ES response is available:

```ts
const {
body, // response payload
statusCode, // http status code of the response
headers, // response headers
warnings, // warnings returned from ES
meta // meta information about the request, such as request parameters, number of attempts and so on
} = await client.asInternalUser.get({ id: 'id' });
```

- all API methods are now generic to allow specifying the response body type

before:

```ts
const body: GetResponse = await legacyClient.callAsInternalUser('get', { id: 'id' });
```

after:

```ts
// body is of type `GetResponse`
const { body } = await client.asInternalUser.get<GetResponse>({ id: 'id' });
mshustov marked this conversation as resolved.
Show resolved Hide resolved
// fallback to `Record<string, any>` if unspecified
const { body } = await client.asInternalUser.get({ id: 'id' });
```

- the returned error types changed

There are no longer specific errors for every HTTP status code (such as `BadRequest` or `NotFound`). A generic
`ResponseError` with the specific `statusCode` is thrown instead.

before:

```ts
import { errors } from 'elasticsearch';
try {
await legacyClient.callAsInternalUser('ping');
} catch(e) {
if(e instanceof errors.NotFound) {
// do something
}
}
```

after:

```ts
import { errors } from '@elastic/elasticsearch';
try {
await client.asInternalUser.ping();
} catch(e) {
if(e instanceof errors.ResponseError && e.statusCode === 404) {
// do something
}
// also possible, as all errors got a name property with the name of the class,
// so this slightly better in term of performances
if(e.name === 'ResponseError' && e.statusCode === 404) {
// do something
}
}
```

- the parameter property names changed from camelCase to snake_case

Even if technically, the javascript client accepts both formats, the typescript definitions are only defining the snake_case
properties.

before:

```ts
legacyClient.callAsCurrentUser('get', {
id: 'id',
storedFields: ['some', 'fields'],
})
```

after:

```ts
client.asCurrentUser.get({
id: 'id',
stored_fields: ['some', 'fields'],
})
```

- the request abortion API changed

All promises returned from the client API calls now have an `abort` method that can be used to cancel the request.

before:

```ts
const controller = new AbortController();
legacyClient.callAsCurrentUser('ping', {}, {
signal: controller.signal,
})
// later
controller.abort();
```

after:

```ts
const request = client.asCurrentUser.ping();
// later
request.abort();
```

- it is now possible to override headers when performing specific API calls.

Note that doing so is strongly discouraged due to potential side effects with the ES service internal
behavior when scoping as the internal or as the current user.

```ts
const request = client.asCurrentUser.ping({}, {
headers: {
authorization: 'foo',
custom: 'bar',
}
});
```

Please refer to the [Breaking changes list](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/breaking-changes.html)
mshustov marked this conversation as resolved.
Show resolved Hide resolved
for more information about the changes between the legacy and new client.

### Accessing the client from a route handler

Apart from the API format change, accessing the client from within a route handler
did not change. As it was done for the legacy client, a preconfigured scoped client
bound to the request is accessible using `core` context provider:

before:

```ts
router.get(
{
path: '/my-route',
},
async (context, req, res) => {
const { client } = context.core.elasticsearch.legacy;
// call as current user
const res = await client.callAsCurrentUser('ping');
// call as internal user
const res2 = await client.callAsInternalUser('search', options);
return res.ok({ body: 'ok' });
}
);
```

after:

```ts
router.get(
{
path: '/my-route',
},
async (context, req, res) => {
const { client } = context.core.elasticsearch;
// call as current user
const res = await client.asCurrentUser.ping();
// call as internal user
const res2 = await client.asInternalUser.search(options);
return res.ok({ body: 'ok' });
}
);
```

### Creating a custom client

Note that the `plugins` option is now longer available on the new client. As the API is now exhaustive, adding custom
endpoints using plugins should no longer be necessary.

The API to create custom clients did not change much:

before:

```ts
const customClient = coreStart.elasticsearch.legacy.createClient('my-custom-client', customConfig);
// do something with the client, such as
await customClient.callAsInternalUser('ping');
// custom client are closable
customClient.close();
```

after:

```ts
const customClient = coreStart.elasticsearch.createClient('my-custom-client', customConfig);
// do something with the client, such as
await customClient.asInternalUser.ping();
// custom client are closable
customClient.close();
```

If, for any reasons, one still needs to reach an endpoint not listed on the client API, using `request.transport`
is still possible:

```ts
const { body } = await client.asCurrentUser.transport.request({
method: 'get',
path: '/my-custom-endpoint',
body: { my: 'payload'},
querystring: { param: 'foo' }
})
```

Remark: the new client creation API is now only available from the `start` contract of the elasticsearch service.