mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
## Summary This PR aims at relocating some of the Kibana modules (plugins and packages) into a new folder structure, according to the _Sustainable Kibana Architecture_ initiative. > [!IMPORTANT] > * We kindly ask you to: > * Manually fix the errors in the error section below (if there are any). > * Search for the `packages[\/\\]` and `plugins[\/\\]` patterns in the source code (Babel and Eslint config files), and update them appropriately. > * Manually review `.buildkite/scripts/pipelines/pull_request/pipeline.ts` to ensure that any CI pipeline customizations continue to be correctly applied after the changed path names > * Review all of the updated files, specially the `.ts` and `.js` files listed in the sections below, as some of them contain relative paths that have been updated. > * Think of potential impact of the move, including tooling and configuration files that can be pointing to the relocated modules. E.g.: > * customised eslint rules > * docs pointing to source code > [!NOTE] > * This PR has been auto-generated. > * Any manual contributions will be lost if the 'relocate' script is re-run. > * Try to obtain the missing reviews / approvals before applying manual fixes, and/or keep your changes in a .patch / git stash. > * Please use [#sustainable_kibana_architecture](https://elastic.slack.com/archives/C07TCKTA22E) Slack channel for feedback. Are you trying to rebase this PR to solve merge conflicts? Please follow the steps describe [here](https://elastic.slack.com/archives/C07TCKTA22E/p1734019532879269?thread_ts=1734019339.935419&cid=C07TCKTA22E). #### 12 plugin(s) are going to be relocated: | Id | Target folder | | -- | ------------- | | `@kbn/data-view-editor-plugin` | `src/platform/plugins/shared/data_view_editor` | | `@kbn/data-view-field-editor-plugin` | `src/platform/plugins/shared/data_view_field_editor` | | `@kbn/data-view-management-plugin` | `src/platform/plugins/shared/data_view_management` | | `@kbn/data-views-plugin` | `src/platform/plugins/shared/data_views` | | `@kbn/discover-enhanced-plugin` | `x-pack/platform/plugins/private/discover_enhanced` | | `@kbn/discover-plugin` | `src/platform/plugins/shared/discover` | | `@kbn/discover-shared-plugin` | `src/platform/plugins/shared/discover_shared` | | `@kbn/field-formats-plugin` | `src/platform/plugins/shared/field_formats` | | `@kbn/saved-objects-finder-plugin` | `src/platform/plugins/shared/saved_objects_finder` | | `@kbn/saved-search-plugin` | `src/platform/plugins/shared/saved_search` | | `@kbn/unified-doc-viewer-plugin` | `src/platform/plugins/shared/unified_doc_viewer` | | `@kbn/unified-histogram-plugin` | `src/platform/plugins/shared/unified_histogram` | #### 18 packages(s) are going to be relocated: | Id | Target folder | | -- | ------------- | | `@kbn/content-management-utils` | `src/platform/packages/shared/kbn-content-management-utils` | | `@kbn/data-view-utils` | `src/platform/packages/shared/kbn-data-view-utils` | | `@kbn/datemath` | `src/platform/packages/shared/kbn-datemath` | | `@kbn/deeplinks-analytics` | `src/platform/packages/shared/deeplinks/analytics` | | `@kbn/default-nav-analytics` | `src/platform/packages/private/default-nav/analytics` | | `@kbn/discover-utils` | `src/platform/packages/shared/kbn-discover-utils` | | `@kbn/es-query` | `src/platform/packages/shared/kbn-es-query` | | `@kbn/field-types` | `src/platform/packages/shared/kbn-field-types` | | `@kbn/field-utils` | `src/platform/packages/shared/kbn-field-utils` | | `@kbn/react-field` | `src/platform/packages/shared/kbn-react-field` | | `@kbn/resizable-layout` | `src/platform/packages/shared/kbn-resizable-layout` | | `@kbn/search-errors` | `src/platform/packages/shared/kbn-search-errors` | | `@kbn/search-response-warnings` | `src/platform/packages/shared/kbn-search-response-warnings` | | `@kbn/search-types` | `src/platform/packages/shared/kbn-search-types` | | `@kbn/unified-data-table` | `src/platform/packages/shared/kbn-unified-data-table` | | `@kbn/unified-doc-viewer` | `src/platform/packages/shared/kbn-unified-doc-viewer` | | `@kbn/unified-field-list` | `src/platform/packages/shared/kbn-unified-field-list` | | `@kbn/unsaved-changes-badge` | `src/platform/packages/private/kbn-unsaved-changes-badge` | <details > <summary>Updated references</summary> ``` ./.buildkite/scripts/steps/functional/scout_ui_tests.sh ./.eslintrc.js ./.i18nrc.json ./docs/developer/advanced/sharing-saved-objects.asciidoc ./docs/developer/architecture/core/saved-objects-service.asciidoc ./docs/developer/best-practices/navigation.asciidoc ./docs/developer/contributing/development-unit-tests.asciidoc ./docs/developer/plugin-list.asciidoc ./examples/unified_doc_viewer/README.md ./examples/unified_field_list_examples/public/plugin.ts ./legacy_rfcs/text/0015_bazel.md ./oas_docs/scripts/merge_ess_oas.js ./oas_docs/scripts/merge_serverless_oas.js ./package.json ./packages/kbn-repo-packages/package-map.json ./packages/kbn-synthetic-package-map/synthetic-packages.json ./packages/kbn-test/src/functional_tests/lib/babel_register_for_test_plugins.js ./packages/kbn-ts-projects/config-paths.json ./packages/kbn-ui-shared-deps-src/BUILD.bazel ./packages/kbn-unified-field-list/src/services/field_examples_calculator/field_examples_calculator.ts ./packages/shared-ux/prompt/no_data_views/types/index.d.ts ./src/dev/code_coverage/ingest_coverage/__tests__/mocks/team_assign_mock.txt ./src/dev/storybook/aliases.ts ./src/platform/packages/private/default-nav/analytics/jest.config.js ./src/platform/packages/private/kbn-unsaved-changes-badge/jest.config.js ./src/platform/packages/shared/deeplinks/analytics/jest.config.js ./src/platform/packages/shared/kbn-content-management-utils/jest.config.js ./src/platform/packages/shared/kbn-data-view-utils/jest.config.js ./src/platform/packages/shared/kbn-datemath/jest.config.js ./src/platform/packages/shared/kbn-discover-utils/jest.config.js ./src/platform/packages/shared/kbn-es-query/jest.config.js ./src/platform/packages/shared/kbn-field-types/jest.config.js ./src/platform/packages/shared/kbn-field-utils/jest.config.js ./src/platform/packages/shared/kbn-react-field/jest.config.js ./src/platform/packages/shared/kbn-resizable-layout/jest.config.js ./src/platform/packages/shared/kbn-search-errors/jest.config.js ./src/platform/packages/shared/kbn-search-response-warnings/jest.config.js ./src/platform/packages/shared/kbn-search-types/jest.config.js ./src/platform/packages/shared/kbn-unified-data-table/jest.config.js ./src/platform/packages/shared/kbn-unified-doc-viewer/jest.config.js ./src/platform/packages/shared/kbn-unified-field-list/jest.config.js ./src/platform/plugins/shared/data_view_editor/jest.config.js ./src/platform/plugins/shared/data_view_field_editor/jest.config.js ./src/platform/plugins/shared/data_view_management/jest.config.js ./src/platform/plugins/shared/data_views/jest.config.js ./src/platform/plugins/shared/discover/README.md ./src/platform/plugins/shared/discover/jest.config.js ./src/platform/plugins/shared/discover/public/context_awareness/README.md ./src/platform/plugins/shared/discover_shared/README.md ./src/platform/plugins/shared/discover_shared/jest.config.js ./src/platform/plugins/shared/field_formats/jest.config.js ./src/platform/plugins/shared/saved_objects_finder/jest.config.js ./src/platform/plugins/shared/saved_search/jest.config.js ./src/platform/plugins/shared/unified_doc_viewer/jest.config.js ./src/platform/plugins/shared/unified_histogram/jest.config.js ./tsconfig.base.json ./tsconfig.refs.json ./x-pack/.i18nrc.json ./x-pack/platform/plugins/private/discover_enhanced/jest.config.js ./x-pack/platform/plugins/private/discover_enhanced/ui_tests/README.md ./x-pack/solutions/security/plugins/timelines/common/search_strategy/index_fields/index.ts ./yarn.lock .github/CODEOWNERS ``` </details><details > <summary>Updated relative paths</summary> ``` src/platform/packages/private/default-nav/analytics/jest.config.js:12 src/platform/packages/private/default-nav/analytics/tsconfig.json:2 src/platform/packages/private/kbn-unsaved-changes-badge/jest.config.js:12 src/platform/packages/private/kbn-unsaved-changes-badge/tsconfig.json:2 src/platform/packages/shared/deeplinks/analytics/jest.config.js:12 src/platform/packages/shared/deeplinks/analytics/tsconfig.json:2 src/platform/packages/shared/kbn-content-management-utils/jest.config.js:12 src/platform/packages/shared/kbn-content-management-utils/tsconfig.json:2 src/platform/packages/shared/kbn-data-view-utils/jest.config.js:12 src/platform/packages/shared/kbn-data-view-utils/tsconfig.json:2 src/platform/packages/shared/kbn-datemath/jest.config.js:22 src/platform/packages/shared/kbn-datemath/tsconfig.json:2 src/platform/packages/shared/kbn-discover-utils/jest.config.js:12 src/platform/packages/shared/kbn-discover-utils/tsconfig.json:2 src/platform/packages/shared/kbn-es-query/jest.config.js:12 src/platform/packages/shared/kbn-es-query/tsconfig.json:2 src/platform/packages/shared/kbn-field-types/jest.config.js:12 src/platform/packages/shared/kbn-field-types/tsconfig.json:2 src/platform/packages/shared/kbn-field-utils/jest.config.js:12 src/platform/packages/shared/kbn-field-utils/tsconfig.json:2 src/platform/packages/shared/kbn-react-field/jest.config.js:12 src/platform/packages/shared/kbn-react-field/tsconfig.json:2 src/platform/packages/shared/kbn-resizable-layout/jest.config.js:12 src/platform/packages/shared/kbn-resizable-layout/tsconfig.json:2 src/platform/packages/shared/kbn-search-errors/jest.config.js:12 src/platform/packages/shared/kbn-search-errors/tsconfig.json:2 src/platform/packages/shared/kbn-search-response-warnings/jest.config.js:12 src/platform/packages/shared/kbn-search-response-warnings/tsconfig.json:2 src/platform/packages/shared/kbn-search-types/jest.config.js:12 src/platform/packages/shared/kbn-search-types/tsconfig.json:2 src/platform/packages/shared/kbn-unified-data-table/jest.config.js:12 src/platform/packages/shared/kbn-unified-data-table/tsconfig.json:2 src/platform/packages/shared/kbn-unified-doc-viewer/jest.config.js:12 src/platform/packages/shared/kbn-unified-doc-viewer/tsconfig.json:2 src/platform/packages/shared/kbn-unified-field-list/jest.config.js:12 src/platform/packages/shared/kbn-unified-field-list/tsconfig.json:2 src/platform/plugins/shared/data_view_editor/jest.config.js:12 src/platform/plugins/shared/data_view_editor/tsconfig.json:2 src/platform/plugins/shared/data_view_field_editor/jest.config.js:12 src/platform/plugins/shared/data_view_field_editor/tsconfig.json:2 src/platform/plugins/shared/data_view_field_editor/tsconfig.json:7 src/platform/plugins/shared/data_view_management/jest.config.js:12 src/platform/plugins/shared/data_view_management/tsconfig.json:2 src/platform/plugins/shared/data_views/jest.config.js:12 src/platform/plugins/shared/data_views/tsconfig.json:2 src/platform/plugins/shared/discover/jest.config.js:12 src/platform/plugins/shared/discover/public/application/context/context_app.scss:1 src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.scss:1 src/platform/plugins/shared/discover/public/context_awareness/README.md:118 src/platform/plugins/shared/discover/public/context_awareness/README.md:119 src/platform/plugins/shared/discover/tsconfig.json:10 src/platform/plugins/shared/discover/tsconfig.json:2 src/platform/plugins/shared/discover_shared/jest.config.js:12 src/platform/plugins/shared/discover_shared/tsconfig.json:10 src/platform/plugins/shared/discover_shared/tsconfig.json:2 src/platform/plugins/shared/field_formats/jest.config.js:12 src/platform/plugins/shared/field_formats/tsconfig.json:2 src/platform/plugins/shared/saved_objects_finder/jest.config.js:12 src/platform/plugins/shared/saved_objects_finder/tsconfig.json:2 src/platform/plugins/shared/saved_search/jest.config.js:12 src/platform/plugins/shared/saved_search/tsconfig.json:2 src/platform/plugins/shared/saved_search/tsconfig.json:6 src/platform/plugins/shared/unified_doc_viewer/jest.config.js:12 src/platform/plugins/shared/unified_doc_viewer/tsconfig.json:2 src/platform/plugins/shared/unified_doc_viewer/tsconfig.json:6 src/platform/plugins/shared/unified_histogram/jest.config.js:12 src/platform/plugins/shared/unified_histogram/tsconfig.json:2 src/platform/plugins/shared/unified_histogram/tsconfig.json:6 x-pack/platform/plugins/private/discover_enhanced/jest.config.js:10 x-pack/platform/plugins/private/discover_enhanced/tsconfig.json:2 ``` </details> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
627 lines
32 KiB
Text
627 lines
32 KiB
Text
[[sharing-saved-objects]]
|
|
== Sharing saved objects
|
|
|
|
This guide describes the "Sharing saved objects" effort, and the breaking changes that plugin developers need to be aware of for the planned
|
|
8.0 release of {kib}. It also describes how developers can take advantage of this feature.
|
|
|
|
From 8.7.0, as a step towards _zero downtime upgrades_, plugins are no longer allowed to update existing single space saved object types to become shareable.
|
|
Note that new saved object types can still be defined as `'multiple'` or `'multiple-isolated'`.
|
|
|
|
[[sharing-saved-objects-overview]]
|
|
=== Overview
|
|
|
|
<<saved-objects-service, Saved objects>> (hereinafter "objects") are used to store all sorts of things in {kib}, from Dashboards to Index
|
|
Patterns to Machine Learning Jobs. The effort to make objects shareable can be summarized in a single picture:
|
|
|
|
image::images/sharing-saved-objects-overview.png["Sharing Saved Objects overview"]
|
|
|
|
Each plugin can register different object types to be used in {kib}. Historically, objects could be _isolated_ (existing in a single
|
|
<<xpack-spaces, space>>) or _global_ (existing in all spaces), there was no in-between. As of the 7.12 release, {kib} now supports two
|
|
additional types of objects:
|
|
|
|
|======================================================================================================
|
|
| | *Where it exists* | *Object IDs* | *Registered as:*
|
|
| Global | All spaces | Globally unique | `namespaceType: 'agnostic'`
|
|
| Isolated | 1 space | Unique in each space | `namespaceType: 'single'`
|
|
| (NEW) Share-capable | 1 space | Globally unique | `namespaceType: 'multiple-isolated'`
|
|
| (NEW) Shareable | 1 or more spaces | Globally unique | `namespaceType: 'multiple'`
|
|
|======================================================================================================
|
|
|
|
Ideally, most types of objects in {kib} will eventually be _shareable_; however, we have also introduced
|
|
<<sharing-saved-objects-faq-share-capable-vs-shareable,_share-capable_ objects>> as a stepping stone for plugin developers to fully support
|
|
this feature.
|
|
|
|
Implementing a shareable saved object type is done in two phases:
|
|
|
|
- **Phase 1**: Convert an existing isolated object type into a share-capable one. Keep reading!
|
|
- **Phase 2**: Switch an existing share-capable object type into a shareable one, _or_ create a new shareable object type. Jump to the
|
|
<<sharing-saved-objects-phase-2>>!
|
|
|
|
[[sharing-saved-objects-breaking-changes]]
|
|
=== Breaking changes
|
|
|
|
To implement this feature, we had to make a key change to how objects are serialized into raw {es} documents. As a result,
|
|
<<sharing-saved-objects-faq-changing-object-ids,some existing object IDs need to be changed>>, and this will cause some breaking changes to
|
|
the way that consumers (plugin developers) interact with objects. We have implemented mitigations so that *these changes will not affect
|
|
end-users _if_ consumers implement the required steps below.*
|
|
|
|
Existing, isolated object types will need to go through a special _conversion process_ to become share-capable upon upgrading {kib} to
|
|
version 8.0. Once objects are converted, they can easily be switched to become fully shareable in any future release. This conversion will
|
|
change the IDs of any existing objects that are not in the Default space. Changing object IDs itself has several knock-on effects:
|
|
|
|
* Nonstandard links to other objects can break - _mitigated by <<sharing-saved-objects-step-1>>_
|
|
* "Deep link" pages (URLs) to objects can break - _mitigated by <<sharing-saved-objects-step-2>> and <<sharing-saved-objects-step-3>>_
|
|
* Encrypted objects may not be able to be decrypted - _mitigated by <<sharing-saved-objects-step-5>>_
|
|
|
|
*To be perfectly clear: these effects will all be mitigated _if and only if_ you follow the steps below!*
|
|
|
|
TIP: External plugins can also convert their objects, but <<sharing-saved-objects-faq-external-plugins,they don't have to do so before the
|
|
8.0 release>>.
|
|
|
|
[[sharing-saved-objects-phase-1]]
|
|
=== Phase 1 developer flowchart
|
|
|
|
If you're still reading this page, you're probably developing a {kib} plugin that registers an object type, and you want to know what steps
|
|
you need to take to prepare for the 8.0 release and mitigate any breaking changes! Depending on how you are using saved objects, you may
|
|
need to take up to 5 steps, which are detailed in separate sections below. Refer to this flowchart:
|
|
|
|
image::images/sharing-saved-objects-phase-1-dev-flowchart.png["Sharing Saved Objects phase 1 - developer flowchart"]
|
|
|
|
TIP: There is a proof-of-concept (POC) pull request to demonstrate these changes. It first adds a simple test plugin that allows users to
|
|
create and view notes. Then, it goes through the steps of the flowchart to convert the isolated "note" objects to become share-capable. As
|
|
you read this guide, you can https://github.com/elastic/kibana/pull/107256[follow along in the POC] to see exactly how to take these steps.
|
|
|
|
[[sharing-saved-objects-q1]]
|
|
==== Question 1
|
|
|
|
> *Do these objects contain links to other objects?*
|
|
|
|
If your objects store _any_ links to other objects (with an object type/ID), you need to take specific steps to ensure that these links
|
|
continue functioning after the 8.0 upgrade.
|
|
|
|
[[sharing-saved-objects-step-1]]
|
|
==== Step 1
|
|
|
|
⚠️ This step *must* be completed no later than the 7.16 release. ⚠️
|
|
|
|
> *Ensure all object links use the root-level `references` field*
|
|
|
|
If you answered "Yes" to <<sharing-saved-objects-q1>>, you need to make sure that your object links are _only_ stored in the root-level
|
|
`references` field. When a given object's ID is changed, this field will be updated accordingly for other objects.
|
|
|
|
The image below shows two different examples of object links from a "case" object to an "action" object. The top shows the incorrect way to
|
|
link to another object, and the bottom shows the correct way.
|
|
|
|
image::images/sharing-saved-objects-step-1.png["Sharing Saved Objects step 1"]
|
|
|
|
If your objects _do not_ use the root-level `references` field, you'll need to <<saved-objects-service-writing-migrations,add a migration>>
|
|
_before the 8.0 release_ to fix that. Here's a migration function for the example above:
|
|
|
|
```ts
|
|
function migrateCaseToV716(
|
|
doc: SavedObjectUnsanitizedDoc<{ connector: { type: string; id: string } }>
|
|
): SavedObjectSanitizedDoc<unknown> {
|
|
const {
|
|
connector: { type: connectorType, id: connectorId, ...otherConnectorAttrs },
|
|
} = doc.attributes;
|
|
const { references = [] } = doc;
|
|
return {
|
|
...doc,
|
|
attributes: {
|
|
...doc.attributes,
|
|
connector: otherConnectorAttrs,
|
|
},
|
|
references: [...references, { type: connectorType, id: connectorId, name: 'connector' }],
|
|
};
|
|
}
|
|
|
|
...
|
|
|
|
// Use this migration function where the "case" object type is registered
|
|
migrations: {
|
|
'7.16.0': migrateCaseToV716,
|
|
},
|
|
```
|
|
|
|
NOTE: Reminder, don't forget to add unit tests and integration tests!
|
|
|
|
[[sharing-saved-objects-q2]]
|
|
==== Question 2
|
|
|
|
> *Are there any "deep links" to these objects?*
|
|
|
|
A deep link is a URL to a page that shows a specific object. End-users may bookmark these URLs or schedule reports with them, so it is
|
|
critical to ensure that these URLs continue working. The image below shows an example of a deep link to a Canvas workpad object:
|
|
|
|
image::images/sharing-saved-objects-q2.png["Sharing Saved Objects deep link example"]
|
|
|
|
Note that some URLs may contain <<sharing-saved-objects-faq-multiple-deep-link-objects,deep links to multiple objects>>, for example, a
|
|
Dashboard _and_ a filter for an Index Pattern.
|
|
|
|
[[sharing-saved-objects-step-2]]
|
|
==== Step 2
|
|
|
|
⚠️ This step will preferably be completed in the 7.16 release; it *must* be completed no later than the 8.0 release. ⚠️
|
|
|
|
> *Update your code to use the new SavedObjectsClient `resolve()` method instead of `get()`*
|
|
|
|
If you answered "Yes" to <<sharing-saved-objects-q2>>, you need to make sure that when you use the SavedObjectsClient to fetch an object
|
|
using its ID, you use a different API to do so. The existing `get()` function will only find an object using its current ID. To make sure
|
|
your existing deep link URLs don't break, you should use the new `resolve()` function; <<sharing-saved-objects-faq-legacy-url-alias,this
|
|
attempts to find an object using its old ID _and_ its current ID>>.
|
|
|
|
In a nutshell, if your deep link page had something like this before:
|
|
|
|
```ts
|
|
const savedObject = savedObjectsClient.get(objType, objId);
|
|
```
|
|
|
|
You'll need to change it to this:
|
|
|
|
```ts
|
|
const resolveResult = savedObjectsClient.resolve(objType, objId);
|
|
const savedObject = resolveResult.saved_object;
|
|
```
|
|
|
|
TIP: See an example of this in https://github.com/elastic/kibana/pull/107256#user-content-example-steps[step 2 of the POC]!
|
|
|
|
The
|
|
{kib-repo}blob/{branch}/packages/core/saved-objects/core-saved-objects-api-server/src/apis/resolve.ts[SavedObjectsResolveResponse
|
|
interface] has four fields, summarized below:
|
|
|
|
* `saved_object` - The saved object that was found.
|
|
* `outcome` - One of the following values: `'exactMatch' | 'aliasMatch' | 'conflict'`
|
|
* `alias_target_id` - This is defined if the outcome is `'aliasMatch'` or `'conflict'`. It means that a legacy URL alias with this ID points
|
|
to an object with a _different_ ID.
|
|
* `alias_purpose` - This is defined if the outcome is `'aliasMatch'` or `'conflict'`. It describes why the legacy URL alis was created.
|
|
|
|
The SavedObjectsClient is available both on the server-side and the client-side. You may be fetching the object on the server-side via a
|
|
custom HTTP route, or you may be fetching it on the client-side directly. Either way, the `outcome` and `alias_target_id` fields need to be
|
|
passed to your client-side code, and you should update your UI accordingly in the next step.
|
|
|
|
NOTE: You don't need to use `resolve()` everywhere, <<sharing-saved-objects-faq-resolve-instead-of-get,you should only use it for deep
|
|
links>>!
|
|
|
|
[[sharing-saved-objects-step-3]]
|
|
==== Step 3
|
|
|
|
⚠️ This step will preferably be completed in the 7.16 release; it *must* be completed no later than the 8.0 release. ⚠️
|
|
|
|
> *Update your _client-side code_ to correctly handle the three different `resolve()` outcomes*
|
|
|
|
The Spaces plugin API exposes React components and functions that you should use to render your UI in a consistent manner for end-users.
|
|
Your UI will need to use the Core HTTP service and the Spaces plugin API to do this.
|
|
|
|
Your page should change <<sharing-saved-objects-faq-resolve-outcomes,according to the outcome>>:
|
|
|
|
image::images/sharing-saved-objects-step-3.png["Sharing Saved Objects resolve outcomes overview"]
|
|
|
|
TIP: See an example of this in https://github.com/elastic/kibana/pull/107256#user-content-example-steps[step 3 of the POC]!
|
|
|
|
1. Update your plugin's `kibana.json` to add a dependency on the Spaces plugin:
|
|
+
|
|
```ts
|
|
...
|
|
"optionalPlugins": ["spaces"]
|
|
```
|
|
|
|
2. Update your plugin's `tsconfig.json` to add a dependency to the Space's plugin's type definitions:
|
|
+
|
|
```ts
|
|
...
|
|
"references": [
|
|
...
|
|
{ "path": "../spaces/tsconfig.json" },
|
|
]
|
|
```
|
|
|
|
3. Update your Plugin class implementation to depend on the Spaces plugin API:
|
|
+
|
|
```ts
|
|
interface PluginStartDeps {
|
|
spaces?: SpacesPluginStart;
|
|
}
|
|
|
|
export class MyPlugin implements Plugin<{}, {}, {}, PluginStartDeps> {
|
|
public setup(core: CoreSetup<PluginStartDeps>) {
|
|
core.application.register({
|
|
...
|
|
async mount(appMountParams: AppMountParameters) {
|
|
const [, pluginStartDeps] = await core.getStartServices();
|
|
const { spaces: spacesApi } = pluginStartDeps;
|
|
...
|
|
// pass `spacesApi` to your app when you render it
|
|
},
|
|
});
|
|
...
|
|
}
|
|
}
|
|
```
|
|
|
|
4. In your deep link page, add a check for the `'aliasMatch'` outcome:
|
|
+
|
|
```ts
|
|
if (spacesApi && resolveResult.outcome === 'aliasMatch') {
|
|
// We found this object by a legacy URL alias from its old ID; redirect the user to the page with its new ID, preserving any URL hash
|
|
const newObjectId = resolveResult.alias_target_id!; // This is always defined if outcome === 'aliasMatch'
|
|
const newPath = `/this/page/${newObjectId}${window.location.hash}`; // Use the *local* path within this app (do not include the "/app/appId" prefix)
|
|
await spacesApi.ui.redirectLegacyUrl({
|
|
path: newPath,
|
|
aliasPurpose: resolveResult.alias_purpose, <1>
|
|
objectNoun: OBJECT_NOUN <2>
|
|
});
|
|
return;
|
|
}
|
|
```
|
|
<1> The `aliasPurpose` field is required as of 8.2, because the API response now includes the reason the alias was created to inform the
|
|
client whether a toast should be shown or not.
|
|
<2> The `objectNoun` field is optional. It just changes "object" in the toast to whatever you specify -- you may want the toast to say
|
|
"dashboard" or "data view" instead.
|
|
|
|
5. And finally, in your deep link page, add a function that will create a callout in the case of a `'conflict'` outcome:
|
|
+
|
|
```tsx
|
|
const getLegacyUrlConflictCallout = () => {
|
|
// This function returns a callout component *if* we have encountered a "legacy URL conflict" scenario
|
|
if (spacesApi && resolveResult.outcome === 'conflict') {
|
|
// We have resolved to one object, but another object has a legacy URL alias associated with this ID/page. We should display a
|
|
// callout with a warning for the user, and provide a way for them to navigate to the other object.
|
|
const currentObjectId = savedObject.id;
|
|
const otherObjectId = resolveResult.alias_target_id!; // This is always defined if outcome === 'conflict'
|
|
const otherObjectPath = `/this/page/${otherObjectId}${window.location.hash}`; // Use the *local* path within this app (do not include the "/app/appId" prefix)
|
|
return (
|
|
<>
|
|
{spacesApi.ui.components.getLegacyUrlConflict({
|
|
objectNoun: OBJECT_NOUN,
|
|
currentObjectId,
|
|
otherObjectId,
|
|
otherObjectPath,
|
|
})}
|
|
<EuiSpacer />
|
|
</>
|
|
);
|
|
}
|
|
return null;
|
|
};
|
|
...
|
|
return (
|
|
<EuiPage>
|
|
<EuiPageBody>
|
|
<EuiPageSection>
|
|
{/* If we have a legacy URL conflict callout to display, show it at the top of the page */}
|
|
{getLegacyUrlConflictCallout()}
|
|
<EuiPageHeader>
|
|
...
|
|
);
|
|
```
|
|
|
|
6. https://github.com/elastic/kibana/pull/107099#issuecomment-891147792[Generate staging data and test your page's behavior with the
|
|
different outcomes.]
|
|
|
|
NOTE: Reminder, don't forget to add unit tests and functional tests!
|
|
|
|
[[sharing-saved-objects-step-4]]
|
|
==== Step 4
|
|
|
|
⚠️ This step *must* be completed in the 8.0 release (no earlier and no later). ⚠️
|
|
|
|
> *Update your _server-side code_ to convert these objects to become "share-capable"*
|
|
|
|
After <<sharing-saved-objects-step-3>> is complete, you can add the code to convert your objects.
|
|
|
|
WARNING: The previous steps can be backported to the 7.x branch, but this step -- the conversion itself -- can only take place in 8.0!
|
|
You should use a separate pull request for this.
|
|
|
|
When you register your object, you need to change the `namespaceType` and also add a `convertToMultiNamespaceTypeVersion` field. This
|
|
special field will trigger the actual conversion that will take place during the Core migration upgrade process when a user installs the
|
|
Kibana 8.0 release:
|
|
|
|
image::images/sharing-saved-objects-step-4.png["Sharing Saved Objects conversion code"]
|
|
|
|
TIP: See an example of this in https://github.com/elastic/kibana/pull/107256#user-content-example-steps[step 4 of the POC]!
|
|
|
|
NOTE: Reminder, don't forget to add integration tests!
|
|
|
|
[[sharing-saved-objects-q3]]
|
|
==== Question 3
|
|
|
|
> *Are these objects encrypted?*
|
|
|
|
Saved objects can optionally be <<xpack-security-secure-saved-objects,encrypted>> by using the Encrypted Saved Objects plugin. Very few
|
|
object types are encrypted, so most plugin developers will not be affected.
|
|
|
|
[[sharing-saved-objects-step-5]]
|
|
==== Step 5
|
|
|
|
⚠️ This step *must* be completed in the 8.0 release (no earlier and no later). ⚠️
|
|
|
|
> *Update your _server-side code_ to add an Encrypted Saved Object (ESO) migration for these objects*
|
|
|
|
If you answered "Yes" to <<sharing-saved-objects-q3>>, you need to take additional steps to make sure that your objects can still be
|
|
decrypted after the conversion process. Encrypted saved objects use some fields as part of "additionally authenticated data" (AAD) to defend
|
|
against different types of cryptographic attacks. The object ID is part of this AAD, and so it follows that the after the object's ID is
|
|
changed, the object will not be able to be decrypted with the standard process.
|
|
|
|
To mitigate this, you need to add a "no-op" ESO migration that will be applied immediately after the object is converted during the 8.0
|
|
upgrade process. This will decrypt the object using its old ID and then re-encrypt it using its new ID:
|
|
|
|
image::images/sharing-saved-objects-step-5.png["Sharing Saved Objects ESO migration"]
|
|
|
|
NOTE: Reminder, don't forget to add unit tests and integration tests!
|
|
|
|
[[sharing-saved-objects-phase-2]]
|
|
=== Phase 2 developer flowchart
|
|
|
|
This section covers switching a share-capable object type into a shareable one _or_ creating a new shareable saved object type. Refer to
|
|
this flowchart:
|
|
|
|
image::images/sharing-saved-objects-phase-2-dev-flowchart.png["Sharing Saved Objects phase 2 - developer flowchart"]
|
|
|
|
[[sharing-saved-objects-step-6]]
|
|
==== Step 6
|
|
|
|
> *Update your _server-side code_ to mark these objects as "shareable"*
|
|
|
|
When you register your object, you need to set the proper `namespaceType`. If you have an existing object type that is "share-capable", you
|
|
can simply change it:
|
|
|
|
image::images/sharing-saved-objects-step-6.png["Sharing Saved Objects registration (shareable)"]
|
|
|
|
[[sharing-saved-objects-step-7]]
|
|
==== Step 7
|
|
|
|
> *Update saved object delete API usage to handle multiple spaces*
|
|
|
|
If an object is shared to multiple spaces, it cannot be deleted without using the
|
|
{kib-repo}blob/{branch}/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete.ts[`force`
|
|
delete option]. You should always be aware when a saved object exists in multiple spaces, and you should warn users in that case.
|
|
|
|
If your UI allows users to delete your objects, you can define a warning message like this:
|
|
|
|
```tsx
|
|
const { namespaces, id } = savedObject;
|
|
const warningMessage =
|
|
namespaces.length > 1 || namespaces.includes('*') ? (
|
|
<FormattedMessage
|
|
id="myPlugin.deleteObjectWarning"
|
|
defaultMessage="When you delete this object, you remove it from every space it is shared in. You can't undo this action."
|
|
/>
|
|
) : null;
|
|
```
|
|
|
|
The <<data-views,Data Views page>> in <<management>> uses a
|
|
https://github.com/elastic/kibana/blob/{branch}/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/edit_index_pattern.tsx[similar
|
|
approach] to show a warning in its delete confirmation modal:
|
|
|
|
image::images/sharing-saved-objects-step-7.png["Sharing Saved Objects deletion warning"]
|
|
|
|
[[sharing-saved-objects-step-8]]
|
|
==== Step 8
|
|
|
|
> *Allow users to view and change assigned spaces for your objects*
|
|
|
|
Users will need a way to view what spaces your objects are currently assigned to and share them to additional spaces. You can accomplish
|
|
this in two ways, and many consumers will want to implement both:
|
|
|
|
1. (Highly recommended) Add reusable components to your application, making it "space-aware". The space-related components are exported by
|
|
the spaces plugin, and you can use them in your own application.
|
|
+
|
|
First, make sure your page contents are wrapped in a
|
|
https://github.com/elastic/kibana/blob/{branch}/x-pack/plugins/spaces/public/spaces_context/types.ts[spaces context provider]:
|
|
+
|
|
```tsx
|
|
const ContextWrapper = useMemo(
|
|
() =>
|
|
spacesApi ? spacesApi.ui.components.getSpacesContextProvider : getEmptyFunctionComponent,
|
|
[spacesApi]
|
|
);
|
|
|
|
...
|
|
|
|
return (
|
|
<ContextWrapper feature='my-feature-id'>
|
|
<!-- your page contents here -->
|
|
</ContextWrapper>
|
|
);
|
|
```
|
|
+
|
|
Second, display a https://github.com/elastic/kibana/blob/{branch}/x-pack/plugins/spaces/public/space_list/types.ts[list of spaces] for an
|
|
object, and third, show a
|
|
https://github.com/elastic/kibana/blob/{branch}/x-pack/plugins/spaces/public/share_saved_objects_to_space/types.ts[flyout] for the user to
|
|
edit the object's assigned spaces. You may want to follow the example of the <<data-views,Data Views page>> and
|
|
https://github.com/elastic/kibana/blob/{branch}/src/platform/plugins/shared/data_view_management/public/components/index_pattern_table/spaces_list.tsx[combine
|
|
these into a single component] so that the space list can be clicked to show the flyout:
|
|
+
|
|
```tsx
|
|
const [showFlyout, setShowFlyout] = useState(false);
|
|
const LazySpaceList = useCallback(spacesApi.ui.components.getSpaceList, [spacesApi]);
|
|
const LazyShareToSpaceFlyout = useCallback(spacesApi.ui.components.getShareToSpaceFlyout, [spacesApi]);
|
|
|
|
const shareToSpaceFlyoutProps: ShareToSpaceFlyoutProps = {
|
|
savedObjectTarget: {
|
|
type: myObject.type,
|
|
namespaces: myObject.namespaces,
|
|
id: myObject.id,
|
|
icon: 'beaker', <1>
|
|
title: myObject.attributes.title, <2>
|
|
noun: OBJECT_NOUN, <3>
|
|
},
|
|
onUpdate: () => { /* callback when the object is updated */ },
|
|
onClose: () => setShowFlyout(false),
|
|
};
|
|
|
|
const canAssignSpaces = !capabilities || !!capabilities.savedObjectsManagement.shareIntoSpace;
|
|
const clickProperties = canAssignSpaces
|
|
? { cursorStyle: 'pointer', listOnClick: () => setShowFlyout(true) }
|
|
: { cursorStyle: 'not-allowed' };
|
|
return (
|
|
<>
|
|
<LazySpaceList
|
|
namespaces={spaceIds}
|
|
displayLimit={8}
|
|
behaviorContext="outside-space" <4>
|
|
{...clickProperties}
|
|
/>
|
|
{showFlyout && <LazyShareToSpaceFlyout {...shareToSpaceFlyoutProps} />}
|
|
</>
|
|
);
|
|
```
|
|
<1> The `icon` field is optional. It specifies an https://elastic.github.io/eui/#/display/icons[EUI icon] type that will be displayed in the
|
|
flyout header.
|
|
<2> The `title` field is optional. It specifies a human-readable identifier for your object that will be displayed in the flyout header.
|
|
<3> The `noun` field is optional. It just changes "object" in the flyout to whatever you specify -- you may want the flyout to say
|
|
"dashboard" or "data view" instead.
|
|
<4> The `behaviorContext` field is optional. It controls how the space list is displayed. When using an `"outside-space"` behavior context,
|
|
the space list is rendered outside of any particular space, so the active space is included in the list. On the other hand, when using a
|
|
`"within-space"` behavior context, the space list is rendered within the active space, so the active space is excluded from the list.
|
|
|
|
2. Allow users to access your objects in the <<managing-saved-objects,Saved Objects Management page>> in <<management>>. You can do this by
|
|
ensuring that your objects are marked as
|
|
{kib-repo}blob/{branch}/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_management.ts[importable and exportable] in your <<saved-objects-type-registration,saved object type registration>>:
|
|
+
|
|
```ts
|
|
name: 'my-object-type',
|
|
management: {
|
|
isImportableAndExportable: true,
|
|
},
|
|
...
|
|
```
|
|
If you do this, then your objects will be visible in the <<managing-saved-objects,Saved Objects Management page>>, where users can assign
|
|
them to multiple spaces.
|
|
|
|
[[sharing-saved-objects-faq]]
|
|
=== Frequently asked questions (FAQ)
|
|
|
|
[[sharing-saved-objects-faq-share-capable-vs-shareable]]
|
|
==== 1. Why are there both "share-capable" and "shareable" object types?
|
|
|
|
We implemented the share-capable object type as an intermediate step for consumers who currently have isolated objects, but are not yet
|
|
ready to support fully shareable objects. This is primarily because we want to make sure all object types are converted at the same time in
|
|
the 8.0 release to minimize confusion and disruption for the end-user experience.
|
|
|
|
We realize that the conversion process and all that it entails can be a not-insignificant amount of work for some Kibana teams to prepare
|
|
for by the 8.0 release. As long as an object is made share-capable, that ensures that its ID will be globally unique, so it will be trivial
|
|
to make that object shareable later on when the time is right.
|
|
|
|
A developer can easily flip a switch to make a share-capable object into a shareable one, since these are both serialized the same way.
|
|
However, we envision that each consumer will need to enact their own plan and make additional UI changes when making an object shareable.
|
|
For example, some users may not have access to the Saved Objects Management page, but we still want those users to be able to see what
|
|
space(s) their objects exist in and share them to other spaces. Each application should add the appropriate UI controls to handle this.
|
|
|
|
|
|
[[sharing-saved-objects-faq-changing-object-ids]]
|
|
==== 2. Why do object IDs need to be changed?
|
|
|
|
This is because of how isolated objects are serialized to raw Elasticsearch documents. Each raw document ID today contains its space ID
|
|
(_namespace_) as a prefix. When objects are copied or imported to other spaces, they keep the same object ID, they just have a different
|
|
prefix when they are serialized to Elasticsearch. This has resulted in a situation where many Kibana installations have saved objects in
|
|
different spaces with the same object ID:
|
|
|
|
image::images/sharing-saved-objects-faq-changing-object-ids-1.png["Sharing Saved Objects object ID diagram (before conversion)"]
|
|
|
|
Once an object is converted, we need to remove this prefix. Because of limitations with our migration process, we cannot actively check if
|
|
this would result in a conflict. Therefore, we decided to pre-emptively regenerate the object ID for every object in a non-Default space to
|
|
ensure that every object ID becomes globally unique:
|
|
|
|
image::images/sharing-saved-objects-faq-changing-object-ids-2.png["Sharing Saved Objects object ID diagram (after conversion)"]
|
|
|
|
[[sharing-saved-objects-faq-multiple-deep-link-objects]]
|
|
==== 3. What if one page has deep links to multiple objects?
|
|
|
|
As mentioned in <<sharing-saved-objects-q2>>, some URLs may contain multiple object IDs, effectively deep linking to multiple objects.
|
|
These should be handled on a case-by-case basis at the plugin owner's discretion. A good rule of thumb is:
|
|
|
|
* The "primary" object on the page should always handle the three `resolve()` outcomes as described in <<sharing-saved-objects-step-3>>.
|
|
* Any "secondary" objects on the page may handle the outcomes differently. If the secondary object ID is not important (for example, it just
|
|
functions as a page anchor), it may make more sense to ignore the different outcomes. If the secondary object _is_ important but it is not
|
|
directly represented in the UI, it may make more sense to throw a descriptive error when a `'conflict'` outcome is encountered.
|
|
- Embeddables should use `spacesApi.ui.components.getEmbeddableLegacyUrlConflict` to render conflict errors:
|
|
+
|
|
image::images/sharing-saved-objects-faq-multiple-deep-link-objects-1.png["Sharing Saved Objects embeddable legacy URL conflict"]
|
|
Viewing details shows the user how to disable the alias and fix the problem using the
|
|
<<spaces-api-disable-legacy-url-aliases,_disable_legacy_url_aliases API>>:
|
|
+
|
|
image::images/sharing-saved-objects-faq-multiple-deep-link-objects-2.png["Sharing Saved Objects embeddable legacy URL conflict (showing details)"]
|
|
- If the secondary object is resolved by an external service (such as the index pattern service), the service should simply make the full
|
|
outcome available to consumers.
|
|
|
|
Ideally, if a secondary object on a deep link page resolves to an `'aliasMatch'` outcome, the consumer should redirect the user to a URL
|
|
with the new ID and display a toast message. The reason for this is that we don't want users relying on legacy URL aliases more often than
|
|
necessary. However, such handling of secondary objects is not considered critical for the 8.0 release.
|
|
|
|
[[sharing-saved-objects-faq-legacy-url-alias]]
|
|
==== 4. What is a "legacy URL alias"?
|
|
|
|
As depicted above, when an object is converted to become share-capable, if it exists in a non-Default space, its ID gets changed. To
|
|
preserve its old ID, we also create a special object called a <<legacy-url-aliases,_legacy URL alias_>> ("alias" for short); this alias
|
|
retains the target object's old ID (_sourceId_), and it contains a pointer to the target object's new ID (_targetId_).
|
|
|
|
Aliases are meant to be mostly invisible to end-users by design. There is no UI to manage them directly. Our vision is that aliases will be
|
|
used as a stop-gap to help us through the 8.0 upgrade process, but we will nudge users away from relying on aliases so we can eventually
|
|
deprecate and remove them.
|
|
|
|
[[sharing-saved-objects-faq-resolve-outcomes]]
|
|
==== 5. Why are there three different resolve outcomes?
|
|
|
|
The `resolve()` function checks both if an object with the given ID exists, _and_ if an object has an alias with the given ID.
|
|
|
|
1. If only the former is true, the outcome is an `'exactMatch'` -- we found the exact object we were looking for.
|
|
2. If only the latter is true, the outcome is an `'aliasMatch'` -- we found an alias with this ID, that pointed us to an object with a
|
|
different ID.
|
|
3. Finally, if _both conditions_ are true, the outcome is a `'conflict'` -- we found two objects using this ID. Instead of returning an
|
|
error in this situation, in the interest of usability, we decided to return the _most correct match_, which is the exact match. By informing
|
|
the consumer that this is a conflict, the consumer can render an appropriate UI to the end-user explaining that this might not be the object
|
|
they are actually looking for.
|
|
|
|
*Outcome 1*
|
|
|
|
When you resolve an object with its current ID, the outcome is an `'exactMatch'`:
|
|
|
|
image::images/sharing-saved-objects-faq-resolve-outcomes-1.png["Sharing Saved Objects resolve outcome 1 (exactMatch)"]
|
|
|
|
This can happen in the Default space _and_ in non-Default spaces.
|
|
|
|
*Outcome 2*
|
|
|
|
When you resolve an object with its old ID (the ID of its alias), the outcome is an `'aliasMatch'`:
|
|
|
|
image::images/sharing-saved-objects-faq-resolve-outcomes-2.png["Sharing Saved Objects resolve outcome 2 (aliasMatch)"]
|
|
|
|
This outcome can only happen in non-Default spaces.
|
|
|
|
*Outcome 3*
|
|
|
|
The third outcome is an edge case that is a combination of the others. If you resolve an object ID and two objects are found -- one as an
|
|
exact match, the other as an alias match -- the outcome is a `'conflict'`:
|
|
|
|
image::images/sharing-saved-objects-faq-resolve-outcomes-3.png["Sharing Saved Objects resolve outcome 3 (conflict)"]
|
|
|
|
We actually have controls in place to prevent this scenario from happening when you share, import, or copy
|
|
objects. However, this scenario _could_ still happen in a few different situations, if objects are created a certain way or if a user
|
|
tampers with an object's raw ES document. Since we can't 100% rule out this scenario, we must handle it gracefully, but we do expect this
|
|
will be a rare occurrence.
|
|
|
|
It is important to note that when a `'conflict'` occurs, the object that is returned is the "most correct" match -- the one with the ID that
|
|
exactly matches.
|
|
|
|
[[sharing-saved-objects-faq-resolve-instead-of-get]]
|
|
==== 6. Should I always use resolve instead of get?
|
|
|
|
Reading through this guide, you may think it is safer or better to use `resolve()` everywhere instead of `get()`. Actually, we made an
|
|
explicit design decision to add a separate `resolve()` function because we want to limit the affects of and reliance upon legacy URL
|
|
aliases. To that end, we collect anonymous usage data based on how many times `resolve()` is used and the different outcomes are
|
|
encountered. That usage data is less useful is `resolve()` is used more often than necessary.
|
|
|
|
Ultimately, `resolve()` should _only_ be used for data flows that involve a user-controlled deep link to an object. There is no reason to
|
|
change any other data flows to use `resolve()`.
|
|
|
|
[[sharing-saved-objects-faq-external-plugins]]
|
|
==== 7. What about external plugins?
|
|
|
|
External plugins (those not shipped with {kib}) can use this guide to convert any isolated objects to become share-capable or fully
|
|
shareable! If you are an external plugin developer, the steps are the same, but you don't need to worry about getting anything done before a
|
|
specific release. The only thing you need to know is that your plugin cannot convert your objects until the 8.0 release.
|
|
|
|
==== 8. How will users be impacted?
|
|
|
|
Refer to <<saved-object-ids,Saved Object IDs>> documentation for more details how users should expect to be impacted.
|