Sharing saved objects developer guide (#107099)
|
@ -72,3 +72,17 @@ Sometimes Saved Objects end up persisted inside another Saved Object. We call th
|
|||
issues with edits propagating - since an entity can only exist in a single place.
|
||||
Note that from the end user stand point, we don’t use these terms “by reference” and “by value”.
|
||||
|
||||
## Sharing Saved Objects
|
||||
|
||||
Starting in Kibana 7.12, saved objects can be shared to multiple spaces. The "space behavior" is determined for each object type depending
|
||||
on how it is registered.
|
||||
|
||||
If you are adding a **new** object type, when you register it:
|
||||
|
||||
1. Use `namespaceType: 'multiple-isolated'` to make these objects exist in exactly one space
|
||||
2. Use `namespaceType: 'multiple'` to make these objects exist in one *or more* spaces
|
||||
3. Use `namespaceType: 'agnostic'` if you want these objects to always exist in all spaces
|
||||
|
||||
If you have an **existing** "legacy" object type that is not shareable (using `namespaceType: 'single'`), see the [legacy developer guide
|
||||
for Sharing Saved Objects](https://www.elastic.co/guide/en/kibana/master/sharing-saved-objects.html) for details on steps you need to take
|
||||
to make sure this is converted to `namespaceType: 'multiple-isolated'` or `namespaceType: 'multiple'` in the 8.0 release.
|
||||
|
|
|
@ -19,7 +19,7 @@ import { SavedObjectsType } from 'src/core/server';
|
|||
export const dashboardVisualization: SavedObjectsType = {
|
||||
name: 'dashboard_visualization', [1]
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
namespaceType: 'multiple-isolated', [2]
|
||||
mappings: {
|
||||
dynamic: false,
|
||||
properties: {
|
||||
|
@ -41,6 +41,10 @@ export const dashboardVisualization: SavedObjectsType = {
|
|||
[1] Since the name of a Saved Object type forms part of the url path for the public Saved Objects HTTP API,
|
||||
these should follow our API URL path convention and always be written as snake case.
|
||||
|
||||
[2] This field determines "space behavior" -- whether these objects can exist in one space, multiple spaces, or all spaces. This value means
|
||||
that objects of this type can only exist in a single space. See
|
||||
<DocLink id="kibDevDocsSavedObjectsIntro" section="sharing-saved-objects" text="Sharing Saved Objects"/> for more information.
|
||||
|
||||
**src/plugins/my_plugin/server/saved_objects/index.ts**
|
||||
|
||||
```ts
|
||||
|
|
|
@ -70,6 +70,8 @@ The `outcome` field may be any of the following:
|
|||
* `"aliasMatch"` -- One document with a legacy URL alias matched the given ID; in this case the `saved_object.id` field is different than the given ID.
|
||||
* `"conflict"` -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the `saved_object` object is the exact match, and the `saved_object.id` field is the same as the given ID.
|
||||
|
||||
If the outcome is `"aliasMatch"` or `"conflict"`, the response will also include an `alias_target_id` field. This means that an alias was found for another object, and it describes that other object's ID.
|
||||
|
||||
Retrieve a dashboard object in the `testspace` by ID:
|
||||
|
||||
[source,sh]
|
||||
|
@ -125,6 +127,7 @@ The API returns the following:
|
|||
"dashboard": "7.0.0"
|
||||
}
|
||||
},
|
||||
"outcome": "conflict"
|
||||
"outcome": "conflict",
|
||||
"alias_target_id": "05becb88-e214-439a-a2ac-15fc783b5d01"
|
||||
}
|
||||
--------------------------------------------------
|
||||
|
|
After Width: | Height: | Size: 172 KiB |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 66 KiB |
BIN
docs/developer/advanced/images/sharing-saved-objects-q2.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
docs/developer/advanced/images/sharing-saved-objects-step-1.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
docs/developer/advanced/images/sharing-saved-objects-step-3.png
Normal file
After Width: | Height: | Size: 125 KiB |
BIN
docs/developer/advanced/images/sharing-saved-objects-step-4.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
docs/developer/advanced/images/sharing-saved-objects-step-5.png
Normal file
After Width: | Height: | Size: 64 KiB |
|
@ -5,6 +5,7 @@
|
|||
* <<development-es-snapshots>>
|
||||
* <<development-basepath>>
|
||||
* <<upgrading-nodejs>>
|
||||
* <<sharing-saved-objects>>
|
||||
|
||||
include::development-es-snapshots.asciidoc[leveloffset=+1]
|
||||
|
||||
|
@ -12,4 +13,6 @@ include::running-elasticsearch.asciidoc[leveloffset=+1]
|
|||
|
||||
include::development-basepath.asciidoc[leveloffset=+1]
|
||||
|
||||
include::upgrading-nodejs.asciidoc[leveloffset=+1]
|
||||
include::upgrading-nodejs.asciidoc[leveloffset=+1]
|
||||
|
||||
include::sharing-saved-objects.asciidoc[leveloffset=+1]
|
472
docs/developer/advanced/sharing-saved-objects.asciidoc
Normal file
|
@ -0,0 +1,472 @@
|
|||
[[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}.
|
||||
|
||||
[[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.
|
||||
|
||||
[[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-dev-flowchart]]
|
||||
=== 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-dev-flowchart.png["Sharing Saved Objects 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
|
||||
https://github.com/elastic/kibana/blob/master/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md[SavedObjectsResolveResponse
|
||||
interface] has three 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.
|
||||
|
||||
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 Core HTTP service and 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 [coreStart, pluginStartDeps] = await core.getStartServices();
|
||||
const { http } = coreStart;
|
||||
const { spaces: spacesApi } = pluginStartDeps;
|
||||
...
|
||||
// pass `http` and `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 = http.basePath.prepend(
|
||||
`path/to/this/page/${newObjectId}${window.location.hash}`
|
||||
);
|
||||
await spacesApi.ui.redirectLegacyUrl(newPath, OBJECT_NOUN);
|
||||
return;
|
||||
}
|
||||
```
|
||||
_Note that `OBJECT_NOUN` is optional, it just changes "object" in the toast to whatever you specify -- you may want the toast to say
|
||||
"dashboard" or "index pattern" 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 = http.basePath.prepend(
|
||||
`path/to/this/page/${otherObjectId}${window.location.hash}`
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{spacesApi.ui.components.getLegacyUrlConflict({
|
||||
objectNoun: OBJECT_NOUN,
|
||||
currentObjectId,
|
||||
otherObjectId,
|
||||
otherObjectPath,
|
||||
})}
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
...
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
<EuiPageContent>
|
||||
{/* If we have a legacy URL conflict callout to display, show it at the top of the page */}
|
||||
{getLegacyUrlConflictCallout()}
|
||||
<EuiPageContentHeader>
|
||||
...
|
||||
);
|
||||
```
|
||||
|
||||
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-step-6]]
|
||||
=== Step 6
|
||||
|
||||
> *Update your code to make your objects shareable*
|
||||
|
||||
_This is not required for the 8.0 release; this additional information will be added in the near future!_
|
||||
|
||||
[[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.
|
||||
- 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 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 first checks if an object with the given ID exists, and then it checks 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.
|
|
@ -45,7 +45,7 @@ import { SavedObjectsType } from 'src/core/server';
|
|||
export const dashboardVisualization: SavedObjectsType = {
|
||||
name: 'dashboard_visualization', // <1>
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
namespaceType: 'multiple-isolated', // <2>
|
||||
mappings: {
|
||||
dynamic: false,
|
||||
properties: {
|
||||
|
@ -66,6 +66,8 @@ export const dashboardVisualization: SavedObjectsType = {
|
|||
<1> Since the name of a Saved Object type forms part of the url path for the
|
||||
public Saved Objects HTTP API, these should follow our API URL path convention
|
||||
and always be written as snake case.
|
||||
<2> This field determines "space behavior" -- whether these objects can exist in one space, multiple spaces, or all spaces. This value means
|
||||
that objects of this type can only exist in a single space. See <<sharing-saved-objects,Sharing Saved Objects>> for more information.
|
||||
|
||||
.src/plugins/my_plugin/server/saved_objects/index.ts
|
||||
[source,typescript]
|
||||
|
@ -153,6 +155,7 @@ should carefully consider the fields they add to the mappings. Similarly,
|
|||
Saved Object types should never use `dynamic: true` as this can cause an
|
||||
arbitrary amount of fields to be added to the `.kibana` index.
|
||||
|
||||
[[saved-objects-service-writing-migrations]]
|
||||
==== Writing Migrations
|
||||
|
||||
Saved Objects support schema changes between Kibana versions, which we call
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResolvedSimpleSavedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.md) > [aliasTargetId](./kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md)
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResolvedSimpleSavedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.md) > [alias\_target\_id](./kibana-plugin-core-public.resolvedsimplesavedobject.alias_target_id.md)
|
||||
|
||||
## ResolvedSimpleSavedObject.aliasTargetId property
|
||||
## ResolvedSimpleSavedObject.alias\_target\_id property
|
||||
|
||||
The ID of the object that the legacy URL alias points to. This is only defined when the outcome is `'aliasMatch'` or `'conflict'`<!-- -->.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
aliasTargetId?: SavedObjectsResolveResponse['aliasTargetId'];
|
||||
alias_target_id?: SavedObjectsResolveResponse['alias_target_id'];
|
||||
```
|
|
@ -16,7 +16,7 @@ export interface ResolvedSimpleSavedObject<T = unknown>
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [aliasTargetId](./kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md) | <code>SavedObjectsResolveResponse['aliasTargetId']</code> | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is <code>'aliasMatch'</code> or <code>'conflict'</code>. |
|
||||
| [alias\_target\_id](./kibana-plugin-core-public.resolvedsimplesavedobject.alias_target_id.md) | <code>SavedObjectsResolveResponse['alias_target_id']</code> | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is <code>'aliasMatch'</code> or <code>'conflict'</code>. |
|
||||
| [outcome](./kibana-plugin-core-public.resolvedsimplesavedobject.outcome.md) | <code>SavedObjectsResolveResponse['outcome']</code> | The outcome for a successful <code>resolve</code> call is one of the following values:<!-- -->\* <code>'exactMatch'</code> -- One document exactly matched the given ID. \* <code>'aliasMatch'</code> -- One document with a legacy URL alias matched the given ID; in this case the <code>saved_object.id</code> field is different than the given ID. \* <code>'conflict'</code> -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the <code>saved_object</code> object is the exact match, and the <code>saved_object.id</code> field is the same as the given ID. |
|
||||
| [savedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md) | <code>SimpleSavedObject<T></code> | The saved object that was found. |
|
||||
| [saved\_object](./kibana-plugin-core-public.resolvedsimplesavedobject.saved_object.md) | <code>SimpleSavedObject<T></code> | The saved object that was found. |
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResolvedSimpleSavedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.md) > [savedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md)
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ResolvedSimpleSavedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.md) > [saved\_object](./kibana-plugin-core-public.resolvedsimplesavedobject.saved_object.md)
|
||||
|
||||
## ResolvedSimpleSavedObject.savedObject property
|
||||
## ResolvedSimpleSavedObject.saved\_object property
|
||||
|
||||
The saved object that was found.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
savedObject: SimpleSavedObject<T>;
|
||||
saved_object: SimpleSavedObject<T>;
|
||||
```
|
|
@ -1,13 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-public.savedobjectsresolveresponse.md) > [aliasTargetId](./kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md)
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-public.savedobjectsresolveresponse.md) > [alias\_target\_id](./kibana-plugin-core-public.savedobjectsresolveresponse.alias_target_id.md)
|
||||
|
||||
## SavedObjectsResolveResponse.aliasTargetId property
|
||||
## SavedObjectsResolveResponse.alias\_target\_id property
|
||||
|
||||
The ID of the object that the legacy URL alias points to. This is only defined when the outcome is `'aliasMatch'` or `'conflict'`<!-- -->.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
aliasTargetId?: string;
|
||||
alias_target_id?: string;
|
||||
```
|
|
@ -15,7 +15,7 @@ export interface SavedObjectsResolveResponse<T = unknown>
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [aliasTargetId](./kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md) | <code>string</code> | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is <code>'aliasMatch'</code> or <code>'conflict'</code>. |
|
||||
| [alias\_target\_id](./kibana-plugin-core-public.savedobjectsresolveresponse.alias_target_id.md) | <code>string</code> | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is <code>'aliasMatch'</code> or <code>'conflict'</code>. |
|
||||
| [outcome](./kibana-plugin-core-public.savedobjectsresolveresponse.outcome.md) | <code>'exactMatch' | 'aliasMatch' | 'conflict'</code> | The outcome for a successful <code>resolve</code> call is one of the following values:<!-- -->\* <code>'exactMatch'</code> -- One document exactly matched the given ID. \* <code>'aliasMatch'</code> -- One document with a legacy URL alias matched the given ID; in this case the <code>saved_object.id</code> field is different than the given ID. \* <code>'conflict'</code> -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the <code>saved_object</code> object is the exact match, and the <code>saved_object.id</code> field is the same as the given ID. |
|
||||
| [saved\_object](./kibana-plugin-core-public.savedobjectsresolveresponse.saved_object.md) | <code>SavedObject<T></code> | The saved object that was found. |
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-server.savedobjectsresolveresponse.md) > [aliasTargetId](./kibana-plugin-core-server.savedobjectsresolveresponse.aliastargetid.md)
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsResolveResponse](./kibana-plugin-core-server.savedobjectsresolveresponse.md) > [alias\_target\_id](./kibana-plugin-core-server.savedobjectsresolveresponse.alias_target_id.md)
|
||||
|
||||
## SavedObjectsResolveResponse.aliasTargetId property
|
||||
## SavedObjectsResolveResponse.alias\_target\_id property
|
||||
|
||||
The ID of the object that the legacy URL alias points to. This is only defined when the outcome is `'aliasMatch'` or `'conflict'`<!-- -->.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
aliasTargetId?: string;
|
||||
alias_target_id?: string;
|
||||
```
|
|
@ -15,7 +15,7 @@ export interface SavedObjectsResolveResponse<T = unknown>
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [aliasTargetId](./kibana-plugin-core-server.savedobjectsresolveresponse.aliastargetid.md) | <code>string</code> | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is <code>'aliasMatch'</code> or <code>'conflict'</code>. |
|
||||
| [alias\_target\_id](./kibana-plugin-core-server.savedobjectsresolveresponse.alias_target_id.md) | <code>string</code> | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is <code>'aliasMatch'</code> or <code>'conflict'</code>. |
|
||||
| [outcome](./kibana-plugin-core-server.savedobjectsresolveresponse.outcome.md) | <code>'exactMatch' | 'aliasMatch' | 'conflict'</code> | The outcome for a successful <code>resolve</code> call is one of the following values:<!-- -->\* <code>'exactMatch'</code> -- One document exactly matched the given ID. \* <code>'aliasMatch'</code> -- One document with a legacy URL alias matched the given ID; in this case the <code>saved_object.id</code> field is different than the given ID. \* <code>'conflict'</code> -- Two documents matched the given ID, one was an exact match and another with a legacy URL alias; in this case the <code>saved_object</code> object is the exact match, and the <code>saved_object.id</code> field is the same as the given ID. |
|
||||
| [saved\_object](./kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md) | <code>SavedObject<T></code> | The saved object that was found. |
|
||||
|
||||
|
|
|
@ -1149,9 +1149,9 @@ export type ResolveDeprecationResponse = {
|
|||
|
||||
// @public
|
||||
export interface ResolvedSimpleSavedObject<T = unknown> {
|
||||
aliasTargetId?: SavedObjectsResolveResponse['aliasTargetId'];
|
||||
alias_target_id?: SavedObjectsResolveResponse['alias_target_id'];
|
||||
outcome: SavedObjectsResolveResponse['outcome'];
|
||||
savedObject: SimpleSavedObject<T>;
|
||||
saved_object: SimpleSavedObject<T>;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SavedObject" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
|
@ -1506,7 +1506,7 @@ export type SavedObjectsNamespaceType = 'single' | 'multiple' | 'multiple-isolat
|
|||
|
||||
// @public (undocumented)
|
||||
export interface SavedObjectsResolveResponse<T = unknown> {
|
||||
aliasTargetId?: string;
|
||||
alias_target_id?: string;
|
||||
outcome: 'exactMatch' | 'aliasMatch' | 'conflict';
|
||||
saved_object: SavedObject<T>;
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ describe('SavedObjectsClient', () => {
|
|||
http.fetch.mockResolvedValue({
|
||||
saved_object: doc,
|
||||
outcome: 'conflict',
|
||||
aliasTargetId: 'another-id',
|
||||
alias_target_id: 'another-id',
|
||||
} as SavedObjectsResolveResponse);
|
||||
});
|
||||
});
|
||||
|
@ -197,11 +197,11 @@ describe('SavedObjectsClient', () => {
|
|||
|
||||
test('resolves with ResolvedSimpleSavedObject instance', async () => {
|
||||
const result = await savedObjectsClient.resolve(doc.type, doc.id);
|
||||
expect(result.savedObject).toBeInstanceOf(SimpleSavedObject);
|
||||
expect(result.savedObject.type).toBe(doc.type);
|
||||
expect(result.savedObject.get('title')).toBe('Example title');
|
||||
expect(result.saved_object).toBeInstanceOf(SimpleSavedObject);
|
||||
expect(result.saved_object.type).toBe(doc.type);
|
||||
expect(result.saved_object.get('title')).toBe('Example title');
|
||||
expect(result.outcome).toBe('conflict');
|
||||
expect(result.aliasTargetId).toBe('another-id');
|
||||
expect(result.alias_target_id).toBe('another-id');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -441,9 +441,13 @@ export class SavedObjectsClient {
|
|||
|
||||
const path = `${this.getPath(['resolve'])}/${type}/${id}`;
|
||||
const request: Promise<SavedObjectsResolveResponse<T>> = this.savedObjectsFetch(path, {});
|
||||
return request.then(({ saved_object: object, outcome, aliasTargetId }) => {
|
||||
const savedObject = new SimpleSavedObject<T>(this, object);
|
||||
return { savedObject, outcome, aliasTargetId };
|
||||
return request.then((resolveResponse) => {
|
||||
const simpleSavedObject = new SimpleSavedObject<T>(this, resolveResponse.saved_object);
|
||||
return {
|
||||
saved_object: simpleSavedObject,
|
||||
outcome: resolveResponse.outcome,
|
||||
alias_target_id: resolveResponse.alias_target_id,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ export interface ResolvedSimpleSavedObject<T = unknown> {
|
|||
/**
|
||||
* The saved object that was found.
|
||||
*/
|
||||
savedObject: SimpleSavedObject<T>;
|
||||
saved_object: SimpleSavedObject<T>;
|
||||
/**
|
||||
* The outcome for a successful `resolve` call is one of the following values:
|
||||
*
|
||||
|
@ -33,5 +33,5 @@ export interface ResolvedSimpleSavedObject<T = unknown> {
|
|||
/**
|
||||
* The ID of the object that the legacy URL alias points to. This is only defined when the outcome is `'aliasMatch'` or `'conflict'`.
|
||||
*/
|
||||
aliasTargetId?: SavedObjectsResolveResponse['aliasTargetId'];
|
||||
alias_target_id?: SavedObjectsResolveResponse['alias_target_id'];
|
||||
}
|
||||
|
|
|
@ -3512,7 +3512,7 @@ describe('SavedObjectsRepository', () => {
|
|||
expect(result).toEqual({
|
||||
saved_object: expect.objectContaining({ type, id: aliasTargetId }),
|
||||
outcome: 'aliasMatch',
|
||||
aliasTargetId,
|
||||
alias_target_id: aliasTargetId,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -3554,7 +3554,7 @@ describe('SavedObjectsRepository', () => {
|
|||
expect(result).toEqual({
|
||||
saved_object: expect.objectContaining({ type, id }),
|
||||
outcome: 'conflict',
|
||||
aliasTargetId,
|
||||
alias_target_id: aliasTargetId,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1146,7 +1146,7 @@ export class SavedObjectsRepository {
|
|||
// @ts-expect-error MultiGetHit._source is optional
|
||||
saved_object: getSavedObjectFromSource(this._registry, type, id, exactMatchDoc),
|
||||
outcome: 'conflict',
|
||||
aliasTargetId: legacyUrlAlias.targetId,
|
||||
alias_target_id: legacyUrlAlias.targetId,
|
||||
};
|
||||
outcomeStatString = REPOSITORY_RESOLVE_OUTCOME_STATS.CONFLICT;
|
||||
} else if (foundExactMatch) {
|
||||
|
@ -1166,7 +1166,7 @@ export class SavedObjectsRepository {
|
|||
aliasMatchDoc
|
||||
),
|
||||
outcome: 'aliasMatch',
|
||||
aliasTargetId: legacyUrlAlias.targetId,
|
||||
alias_target_id: legacyUrlAlias.targetId,
|
||||
};
|
||||
outcomeStatString = REPOSITORY_RESOLVE_OUTCOME_STATS.ALIAS_MATCH;
|
||||
}
|
||||
|
|
|
@ -328,7 +328,7 @@ export interface SavedObjectsResolveResponse<T = unknown> {
|
|||
/**
|
||||
* The ID of the object that the legacy URL alias points to. This is only defined when the outcome is `'aliasMatch'` or `'conflict'`.
|
||||
*/
|
||||
aliasTargetId?: string;
|
||||
alias_target_id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3029,7 +3029,7 @@ export interface SavedObjectsResolveImportErrorsOptions {
|
|||
|
||||
// @public (undocumented)
|
||||
export interface SavedObjectsResolveResponse<T = unknown> {
|
||||
aliasTargetId?: string;
|
||||
alias_target_id?: string;
|
||||
outcome: 'exactMatch' | 'aliasMatch' | 'conflict';
|
||||
saved_object: SavedObject<T>;
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ export interface SpacesApiUiComponent {
|
|||
* that there is a conflict, and it includes a button that will redirect the user to object B when clicked.
|
||||
*
|
||||
* Consumers need to determine the local path for the new URL on their own, based on the object ID that was used to call
|
||||
* `SavedObjectsClient.resolve()` (A) and the `aliasTargetId` value in the response (B). For example...
|
||||
* `SavedObjectsClient.resolve()` (A) and the `alias_target_id` value in the response (B). For example...
|
||||
*
|
||||
* A is `workpad-123` and B is `workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e`.
|
||||
*
|
||||
|
|
|
@ -85,9 +85,9 @@ export function resolveTestSuiteFactory(esArchiver: any, supertest: SuperTest<an
|
|||
if (!testCase.failure) {
|
||||
expect(response.body.outcome).to.eql(expectedOutcome);
|
||||
if (expectedOutcome === 'conflict' || expectedOutcome === 'aliasMatch') {
|
||||
expect(response.body.aliasTargetId).to.eql(expectedAliasTargetId);
|
||||
expect(response.body.alias_target_id).to.eql(expectedAliasTargetId);
|
||||
} else {
|
||||
expect(response.body.aliasTargetId).to.eql(undefined);
|
||||
expect(response.body.alias_target_id).to.eql(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|