Breaking changes in 7.9
editBreaking changes in 7.9
editThis page discusses the breaking changes that you need to be aware of when migrating your application to Kibana 7.9.
Breaking changes for users
editkibana.keystore
moved from the data folder to the config folder
editkibana.keystore
has moved from the configured path.data
folder to <root>/config
for archive distributions and /etc/kibana
for
package distributions. If a pre-existing keystore exists in the data directory,
that path will continue to be used.
via #57856
Breaking changes for plugin developers
editaborted$ event fixed and completed$ event added to KibanaRequest
The request.events.aborted$
Observable will now properly wait for the
response to be sent before completing.
A new request.events.completed$
API is available that will emit once
a request has been completely handled or aborted.
via #73898
The Management API has a new interface
A public setup
contract has been reduced to just register
. A
new interface, sections
, which is a map of management sections provided by the plugin,
replaces getSection
. Public start interfaces have been removed as all
registration should occur in the setup
lifecycle.
via #71144
Filters from fields with no values are now allowed
Kibana now allows the creation of filters from fields with a null or undefined value.
via #70936
The onPreAuth
and onPreRouting
http interceptors are now separate
The onPreAuth
interceptor has been renamed to onPreRouting
to better
reflect its place in the execution order—it is now called right before the route lookup step.
A new onPreAuth
interceptor is executed before the Auth
lifecycle step,
but after the onPreRouting
step.
via #70775
The Metrics API moved to start
The Metric service API exposed from the setup
contract has been moved
to the start
lifecycle.
via #69787
fieldFormats
removed from AggConfig
and AggConfigs
AggConfig
has been updated to no longer return a field format
instance for the field it is aggregating on. As a result, the fieldFormatter
and
fieldOwnFormatter
methods have been removed. Additionally, the getFormat
method
has been removed from each individual agg type.
If you need to access a field format instance, use the newly-added
AggConfig.toSerializedFieldFormat
or AggType.toSerializedFormat
to retrieve the serializable representation of the field’s format,
and then pass it to the deserialize
method from the field formats service
to get the actual format instance.
class MyPlugin { async start(core, { data }) { const { indexPatterns, fieldFormats, search } = data; const indexPattern = await indexPatterns.get('myId'); const agg = { type: 'terms', params: { field: 'machine.os.keyword' }, }; const aggConfigs = search.aggs.createAggConfigs(indexPattern, [agg]); const termsAgg = aggConfigs.aggs[0]; - const formatter = termsAgg.type.getFormat(termsAgg); - // or - const formatter = termsAgg.fieldFormatter('text'); + const formatter = fieldFormats.deserialize(termsAgg.toSerializedFieldFormat()); + // or + const formatter = fieldFormats.deserialize(termsAgg.type.getSerializedFormat(termsAgg)); const formattedValue = formatter.convert('myValue'); } }
In addition, the legacy formatting helpers that were exported from
ui/visualize/loader/pipeline_helpers/utilities
have been removed.
If your plugin imports from this directory, please update your code to use
the fieldFormats
service directly.
via #69762
New API adds support for migrations for an EncryptedSavedObject
A new createMigration
API on the EncryptedSavedObjectsPluginSetup
facilitates defining a migration for an EncryptedSavedObject type.
Defining migrations
EncryptedSavedObjects
rely on standard SavedObject migrations
,
but due to the additional complexity introduced by the need to decrypt and
reencrypt the migrated document, there are some caveats to how we support this.
Most of this complexity is abstracted away by the plugin, and all you need to do is leverage our API.
The EncryptedSavedObjects
Plugin SetupContract exposes a createMigration
API that facilitates defining a migration for your EncryptedSavedObject type.
The createMigration
function takes four arguments:
Argument | Description | Type | |
---|---|---|---|
|
A predicate that is called for each document, prior to being decrypted, which confirms whether a document requires migration or not. This predicate is important as the decryption step is costly, and we would rather not decrypt and re-encrypt a document if we can avoid it. |
function |
|
|
A migration function which will migrate each decrypted document from the old shape to the new one. |
function |
|
|
Optional. An |
object |
|
|
Optional. An |
object |
Example: Migrating a Value
encryptedSavedObjects.registerType({ type: 'alert', attributesToEncrypt: new Set(['apiKey']), attributesToExcludeFromAAD: new Set(['mutedInstanceIds', 'updatedBy']), }); const migration790 = encryptedSavedObjects.createMigration<RawAlert, RawAlert>( function shouldBeMigrated(doc): doc is SavedObjectUnsanitizedDoc<RawAlert> { return doc.consumer === 'alerting' || doc.consumer === undefined; }, (doc: SavedObjectUnsanitizedDoc<RawAlert>): SavedObjectUnsanitizedDoc<RawAlert> => { const { attributes: { consumer }, } = doc; return { ...doc, attributes: { ...doc.attributes, consumer: consumer === 'alerting' || !consumer ? 'alerts' : consumer, }, }; } );
In the above example, you can see the following:
-
In
shouldBeMigrated
, we limit the migrated alerts to those whoseconsumer
field equalsalerting
or is undefined. -
In the migration function, we migrate the value of
consumer
to the value we want (alerts
orunknown
, depending on the current value). In this function, we can assume that only documents with aconsumer
ofalerting
orundefined
will be passed in, but it’s still safest not to, and so we use the currentconsumer
as the default when needed. - Note that we haven’t passed in any type definitions. This is because we can rely on the registered type, as the migration is changing a value and not the shape of the object.
An EncryptedSavedObject migration is a normal SavedObjects migration, so we can plug it into the underlying SavedObject just like any other kind of migration:
savedObjects.registerType({ name: 'alert', hidden: true, namespaceType: 'single', migrations: { // apply this migration in 7.9.0 '7.9.0': migration790, }, mappings: { //... }, });
Example: Migating a Type
If your migration needs to change the type, for example, by removing an encrypted field, you will have to specify the legacy type for the input.
encryptedSavedObjects.registerType({ type: 'alert', attributesToEncrypt: new Set(['apiKey']), attributesToExcludeFromAAD: new Set(['mutedInstanceIds', 'updatedBy']), }); const migration790 = encryptedSavedObjects.createMigration<RawAlert, RawAlert>( function shouldBeMigrated(doc): doc is SavedObjectUnsanitizedDoc<RawAlert> { return doc.consumer === 'alerting' || doc.consumer === undefined; }, (doc: SavedObjectUnsanitizedDoc<RawAlert>): SavedObjectUnsanitizedDoc<RawAlert> => { const { attributes: { legacyEncryptedField, ...attributes }, } = doc; return { ...doc, attributes: { ...attributes }, }; }, { type: 'alert', attributesToEncrypt: new Set(['apiKey', 'legacyEncryptedField']), attributesToExcludeFromAAD: new Set(['mutedInstanceIds', 'updatedBy']), } );
This example shows how we provide a legacy type that describes the input that needs to be decrypted. The migration function will default to using the registered type to encrypt the migrated document after the migration is applied.
If you need to migrate between two legacy types, you can specify both types at once:
encryptedSavedObjects.registerType({ type: 'alert', attributesToEncrypt: new Set(['apiKey']), attributesToExcludeFromAAD: new Set(['mutedInstanceIds', 'updatedBy']), }); const migration780 = encryptedSavedObjects.createMigration<RawAlert, RawAlert>( function shouldBeMigrated(doc): doc is SavedObjectUnsanitizedDoc<RawAlert> { // ... }, (doc: SavedObjectUnsanitizedDoc<RawAlert>): SavedObjectUnsanitizedDoc<RawAlert> => { // ... }, // legacy input type { type: 'alert', attributesToEncrypt: new Set(['apiKey', 'legacyEncryptedField']), attributesToExcludeFromAAD: new Set(['mutedInstanceIds', 'updatedBy']), }, // legacy migration type { type: 'alert', attributesToEncrypt: new Set(['apiKey', 'legacyEncryptedField']), attributesToExcludeFromAAD: new Set(['mutedInstanceIds', 'updatedBy', 'legacyEncryptedField']), } );
via #69513
Canvas templates now stored as saved objects
Previously, workpad templates were added through the Canvas API client side.
Workpad templates are now stored as saved objects, so an API is no longer required for adding them.
You can add templates through SavedObject
management.
via #69438
Search Typescript improved
The front end search strategy concept is now deprecated and the
following API methods were removed from the data.search
plugin:
-
registerSearchStrategy
-
getSearchStrategy
via #69333
DocLinks API moved from setup
to start
The docLinks service API exposed from the setup
contract has been moved to the start
lifecycle.
via #68745
Plugin API added for customizing the logging configuration
Plugins can now customize the logging configuration on the fly.
import { of } from 'rxjs'; core.logging.configure(of( { appenders: { myCustomAppender: { ... }, }, loggers: [ { context: 'subcontext', appenders: ['myCustomAppender'], level: 'warn' } ] } ))
via #68704
Developer guide restructured
The developer guide includes the following improvements:
- Migrates CONTRIBUTING.md content into AsciiDoc
- Moves CONTRIBUTING content into the developer guide
- Removes outdated content
- Creates the structure proposed in this issue
via #67764
Elasticsearch API exposed from setup
contract is deprecated
The Elasticsearch API exposed from the setup
contract is not available
and will be deleted without notice. Use the core start API instead.
// before setup(core: CoreSetup) { core.elasticsearch.dataClient(...) core.elasticsearch.adminClient(...) } // after setup(core: CoreSetup) { core.elasticsearch.legacy.client(...) }
via #67596
API reference docs available for state_containers and state_sync
The API reference docs for state_sync
and state_containers
are now available:
via #67354
Elasticsearch client exposed via request context marked as deprecated
The Elasticsearch service no longer provides separate data
and admin
clients.
The Elasticsearch service client is marked as deprecated and is superseded by a new one.
// in route handler router.get( ... async function handler (context) { --- return await context.elasticsearch.adminClient.callAsInternalUser('endpoint'); +++ return await context.elasticsearch.legacy.client.callAsInternalUser('endpoint'); }) // in plugin setup(core){ return { async search(id) { --- return await context.elasticsearch.adminClient.callAsInternalUser('endpoint', id); +++ return await context.elasticsearch.legacy.client.callAsInternalUser('endpoint', id); } } }
via #67319
Licensing now uses Elasticsearch from start
contract
The licensing plugin API exposed from the setup
contract
is deprecated in favor of start
contract counterparts:
// before setup(core, plugins){ plugins.licensing.license$.pipe(...) } // after start(core, plugins){ plugins.licensing.license$.pipe(...) }
via #67291
The Actions SavedObject type action
is now a hidden type
Interaction with the Actions SavedObject type requires
you to tell your SavedObjectsClient
to include
the action
hidden type as follows:
core.savedObjects.getScopedClient(request, { includedHiddenTypes: ['action'] })
Do not circumvent the authorization model by accessing these objects directly.
Use AlertsClient
instead.
via #67109
Saved objects now include support for hidden types
Saved objects
The SavedObjectClient’s getScopedClient
, createScopedRepository
and
createInternalRepository
can now take a list of types to include in the underlying repository.
You can use this to create a client that has access to hidden types:
core.savedObjects.getScopedClient(request, { includedHiddenTypes: ['hiddenType'] })
This creates a SavedObjects
client scoped to a user by the specified
request with access to a hidden type called hiddenType
.
Encrypted saved objects
The EncryptedSavedObject
plugin no longer exposes a single client as part of its
start
contract. Instead it exposes a getClient
API that exposes the client API.
The getClient
can also specify a list of hidden types to gain access to which are hidden by default.
For example, given a Kibana platform plugin that has specified encryptedSavedObjects
as a Setup
dependency:
const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient(['hiddenType']); return encryptedSavedObjectsClient.getDecryptedAsInternalUser('hiddenType', '123', { namespace: 'some-namespace' });
via #66879
The alerting
plugin was renamed alerts
to follow the Kibana styleguide
This includes the following API changes:
-
Changed actions
BASE_ALERT_API_PATH
to ` /api/alerts` because according to the styleguide, it should keep the structure/api/plugin_id
-
Changed endpoint
/api/alert/_find
just to/api/alerts/_find
-
Changed
/types
to/list_alert_types
-
Changed POST
/api/alert
to POST/api/alerts/alert
-
Changed GET
/api/alert/{id}
to GET/api/alerts/alert/{id}
-
Changed PUT
/api/alert/{id}
to PUT/api/alerts/alert/{id}
-
Changed DELETE
/api/alert/{id}
to DELETE/api/alerts/alert/{id}
-
Changed GET
/api/alert/{id}/state
to GET/api/alerts/alert/{id}/state
-
Changed POST
/api/alert/{id}/_enable
to POST/api/alerts/alert/{id}/_enable
-
Changed POST
/api/alert/{id}/_disable
to POST/api/alerts/alert/{id}/_disable
-
Changed POST
/api/alert/{id}/_mute_all
to POST/api/alerts/alert/{id}/_mute_all
-
Changed POST
/api/alert/{alertId}/alert_instance/{alertInstanceId}/_mute
to POST/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_mute
-
Changed POST
/api/alert/{id}/_unmute_all
to POST/api/alerts/alert/{id}/_unmute_all
-
Changed POST
/api/alert/{id}/_update_api_key
to POST/api/alerts/alert/{id}/_update_api_key
-
Changed POST
/api/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute
to POST/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute
via #66838
The new platform API is now implemented in Management
This change:
-
Refactors out use of
registerLegacyApp
and uses react-router-dom for routing. - Implements a landing page and sidebar in the Management plugin.
-
Removes the legacy API from
src/plugins/management/public/plugin.ts
and related code.
via #66781
Open source features registration moved to Kibana platform
Kibana now allows the getFeatures
plugin method to be called within the start
lifecycle.
via #66524
SavedObject registration in the legacy platform is not supported
To use SavedObjects, you must move your plugin to the Kibana platform.
// before in the legacy plugin export default function ({ Plugin }) { new Plugin({ id: 'my-plugin', uiExports: { mappings: { 'my-plugin-so': { properties: {...}, }, }, }, }), // in the Kibana platform plugin export class MyPlugin implements Plugin { constructor(context: PluginInitializerContext) {} setup(core: CoreSetup) { core.savedObjects.registerType({ name: 'my-plugin-so', mappings: {...} }); } }
via #66203
Cross-links are now handled automatically
Links from one application to another are now automatically handled by the Kibana platform
to perform the navigation without a full page refresh and the need to
manually add a click handler to call application.navigateToApp
.
You can disable this behavior by adding the data-disable-core-navigation
attribute on the link (a
) element or any of its parent.
This feature is not enabled for legacy applications.
via #65164
Field format editors API migrated to Kibana Platform
Field format editors (used by index pattern management) are no longer added
via the field formatters registry, ui/registry/field_format_editors
. They
are now added via the indexPatternManagement
plugin.
via #65026
The expressions
plugin has a new set of helpers
The expressions
plugin introduces a set of helpers that make it easier to
manipulate expression ASTs. Refer to this PR
for more detailed examples.
// also available on `expressions/public/server` import { buildExpression, buildExpressionFunction } from '../../src/plugins/expressions/public'; // `buildExpression` takes an expression string, AST, or array of `buildExpressionFunction` const exp = buildExpression([ // `buildExpressionFunction` takes an expression function name, and object of args buildExpressionFunction('myFn', { hello: [true] }); ]); const anotherFn = buildExpressionFunction('anotherFn', { world: [false] }); exp.functions.push(anotherFn); fn.replaceArgument('world', [true]); exp.toAst(); // prints the latest AST // you can get added type-safety by providing a generic type argument: const exp = buildExpression([ buildExpressionFunction<MyFnExpressionFunctionDefinition>('myFn', { hello: [true] }); ]); const fns = exp.findFunction<MyFnExpressionFunctionDefinition>('myFn');
via #64395
Mount ui/new_platform
applications in same div structure as Core
Applications that are mounted via the core.application.register
interface from the legacy ui/new_platform
module are now mounted inside a
new div
inside of the <div class="application />
node rather than directly inside that node.
This makes the legacy bridge consistent with how true Kibana platform applications are mounted.
via #63930