Breaking changes in 7.9edit

This page discusses the breaking changes that you need to be aware of when migrating your application to Kibana 7.9.

Breaking changes for usersedit

kibana.keystore moved from the data folder to the config folderedit

kibana.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 developersedit

aborted$ 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

isMigrationNeededPredicate

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

migration

A migration function which will migrate each decrypted document from the old shape to the new one.

function

inputType

Optional. An EncryptedSavedObjectTypeRegistration which describes the ESOType of the input (the document prior to migration). If this type isn’t provided, we’ll assume the input doc follows the registered type.

object

migratedType

Optional. An EncryptedSavedObjectTypeRegistration which describes the ESOType of the output (the document after migration). If this type isn’t provided, we’ll assume the migrated doc follows the registered type.

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 whose consumer field equals alerting or is undefined.
  • In the migration function, we migrate the value of consumer to the value we want (alerts or unknown, depending on the current value). In this function, we can assume that only documents with a consumer of alerting or undefined will be passed in, but it’s still safest not to, and so we use the current consumer 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

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:

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

The Alerting SavedObject type alert is now a hidden type

Interaction with the Alerting SavedObject type requires you to tell your SavedObjectsClient to include the alert hidden type as follows:

core.savedObjects.getScopedClient(request, { includedHiddenTypes: ['alert'] })

Do not circumvent the authorization model by accessing these objects directly. Use AlertsClient instead.

via #66719

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

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