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 whoseconsumerfield equalsalertingor is undefined. -
In the migration function, we migrate the value of
consumerto the value we want (alertsorunknown, depending on the current value). In this function, we can assume that only documents with aconsumerofalertingorundefinedwill be passed in, but it’s still safest not to, and so we use the currentconsumeras 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_PATHto ` /api/alerts` because according to the styleguide, it should keep the structure/api/plugin_id -
Changed endpoint
/api/alert/_findjust to/api/alerts/_find -
Changed
/typesto/list_alert_types -
Changed POST
/api/alertto 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}/stateto GET/api/alerts/alert/{id}/state -
Changed POST
/api/alert/{id}/_enableto POST/api/alerts/alert/{id}/_enable -
Changed POST
/api/alert/{id}/_disableto POST/api/alerts/alert/{id}/_disable -
Changed POST
/api/alert/{id}/_mute_allto POST/api/alerts/alert/{id}/_mute_all -
Changed POST
/api/alert/{alertId}/alert_instance/{alertInstanceId}/_muteto POST/api/alerts/alert/{alertId}/alert_instance/{alertInstanceId}/_mute -
Changed POST
/api/alert/{id}/_unmute_allto POST/api/alerts/alert/{id}/_unmute_all -
Changed POST
/api/alert/{id}/_update_api_keyto POST/api/alerts/alert/{id}/_update_api_key -
Changed POST
/api/alert/{alertId}/alert_instance/{alertInstanceId}/_unmuteto 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
registerLegacyAppand 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.tsand 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