diff --git a/dev_docs/key_concepts/saved_objects.mdx b/dev_docs/key_concepts/saved_objects.mdx index d89342765c8f1..bef92bf028697 100644 --- a/dev_docs/key_concepts/saved_objects.mdx +++ b/dev_docs/key_concepts/saved_objects.mdx @@ -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. diff --git a/dev_docs/tutorials/saved_objects.mdx b/dev_docs/tutorials/saved_objects.mdx index bd7d231218af1..c0e9ca5f571b8 100644 --- a/dev_docs/tutorials/saved_objects.mdx +++ b/dev_docs/tutorials/saved_objects.mdx @@ -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 + for more information. + **src/plugins/my_plugin/server/saved_objects/index.ts** ```ts diff --git a/docs/api/saved-objects/resolve.asciidoc b/docs/api/saved-objects/resolve.asciidoc index f2bf31bc5d9e4..abfad6a0a8150 100644 --- a/docs/api/saved-objects/resolve.asciidoc +++ b/docs/api/saved-objects/resolve.asciidoc @@ -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" } -------------------------------------------------- diff --git a/docs/developer/advanced/images/sharing-saved-objects-dev-flowchart.png b/docs/developer/advanced/images/sharing-saved-objects-dev-flowchart.png new file mode 100644 index 0000000000000..bc829059988db Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-dev-flowchart.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-faq-changing-object-ids-1.png b/docs/developer/advanced/images/sharing-saved-objects-faq-changing-object-ids-1.png new file mode 100644 index 0000000000000..5c41f19392d7b Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-faq-changing-object-ids-1.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-faq-changing-object-ids-2.png b/docs/developer/advanced/images/sharing-saved-objects-faq-changing-object-ids-2.png new file mode 100644 index 0000000000000..50160d7a7b0ff Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-faq-changing-object-ids-2.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-faq-resolve-outcomes-1.png b/docs/developer/advanced/images/sharing-saved-objects-faq-resolve-outcomes-1.png new file mode 100644 index 0000000000000..34733de65859d Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-faq-resolve-outcomes-1.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-faq-resolve-outcomes-2.png b/docs/developer/advanced/images/sharing-saved-objects-faq-resolve-outcomes-2.png new file mode 100644 index 0000000000000..7c9dcb5fb9b05 Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-faq-resolve-outcomes-2.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-faq-resolve-outcomes-3.png b/docs/developer/advanced/images/sharing-saved-objects-faq-resolve-outcomes-3.png new file mode 100644 index 0000000000000..2d4c0c4399d89 Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-faq-resolve-outcomes-3.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-overview.png b/docs/developer/advanced/images/sharing-saved-objects-overview.png new file mode 100644 index 0000000000000..5c08eaa286ae6 Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-overview.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-q2.png b/docs/developer/advanced/images/sharing-saved-objects-q2.png new file mode 100644 index 0000000000000..f453f90c160a9 Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-q2.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-step-1.png b/docs/developer/advanced/images/sharing-saved-objects-step-1.png new file mode 100644 index 0000000000000..56ef017354d39 Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-step-1.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-step-3.png b/docs/developer/advanced/images/sharing-saved-objects-step-3.png new file mode 100644 index 0000000000000..92dd7ebfef88e Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-step-3.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-step-4.png b/docs/developer/advanced/images/sharing-saved-objects-step-4.png new file mode 100644 index 0000000000000..73c5848521a11 Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-step-4.png differ diff --git a/docs/developer/advanced/images/sharing-saved-objects-step-5.png b/docs/developer/advanced/images/sharing-saved-objects-step-5.png new file mode 100644 index 0000000000000..07e74e44d4e67 Binary files /dev/null and b/docs/developer/advanced/images/sharing-saved-objects-step-5.png differ diff --git a/docs/developer/advanced/index.asciidoc b/docs/developer/advanced/index.asciidoc index 5c53bedd95e72..6988f9b7d6128 100644 --- a/docs/developer/advanced/index.asciidoc +++ b/docs/developer/advanced/index.asciidoc @@ -4,9 +4,12 @@ * <> * <> * <> +* <> include::development-es-snapshots.asciidoc[leveloffset=+1] include::running-elasticsearch.asciidoc[leveloffset=+1] -include::development-basepath.asciidoc[leveloffset=+1] \ No newline at end of file +include::development-basepath.asciidoc[leveloffset=+1] + +include::sharing-saved-objects.asciidoc[leveloffset=+1] diff --git a/docs/developer/advanced/sharing-saved-objects.asciidoc b/docs/developer/advanced/sharing-saved-objects.asciidoc new file mode 100644 index 0000000000000..19c1b80657281 --- /dev/null +++ b/docs/developer/advanced/sharing-saved-objects.asciidoc @@ -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 + +<> (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 +<>) 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 +<> 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, +<>, 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 <>_ +* "Deep link" pages (URLs) to objects can break - _mitigated by <> and <>_ +* Encrypted objects may not be able to be decrypted - _mitigated by <>_ + +*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-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 <>, 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 <> +_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 { + 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 <>, 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 <>, 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; <>. + +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-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 <>: + +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) { + 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, + })} + + + ); + } + return null; +}; +... +return ( + + + + {/* If we have a legacy URL conflict callout to display, show it at the top of the page */} + {getLegacyUrlConflictCallout()} + +... +); +``` + +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 <> 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 <> 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 <>, 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 <>, 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 <>. +* 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. diff --git a/docs/developer/architecture/core/saved-objects-service.asciidoc b/docs/developer/architecture/core/saved-objects-service.asciidoc index fa7fc4233259d..a7ce86ea46359 100644 --- a/docs/developer/architecture/core/saved-objects-service.asciidoc +++ b/docs/developer/architecture/core/saved-objects-service.asciidoc @@ -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 <> 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 diff --git a/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.alias_target_id.md similarity index 59% rename from docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md rename to docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.alias_target_id.md index 415681b2bb0d3..0054f533a23d0 100644 --- a/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md +++ b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.alias_target_id.md @@ -1,13 +1,13 @@ -[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'`. Signature: ```typescript -aliasTargetId?: SavedObjectsResolveResponse['aliasTargetId']; +alias_target_id?: SavedObjectsResolveResponse['alias_target_id']; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.md b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.md index 43727d86296a4..4936598c58799 100644 --- a/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.md +++ b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.md @@ -16,7 +16,7 @@ export interface ResolvedSimpleSavedObject | Property | Type | Description | | --- | --- | --- | -| [aliasTargetId](./kibana-plugin-core-public.resolvedsimplesavedobject.aliastargetid.md) | SavedObjectsResolveResponse['aliasTargetId'] | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is 'aliasMatch' or 'conflict'. | +| [alias\_target\_id](./kibana-plugin-core-public.resolvedsimplesavedobject.alias_target_id.md) | SavedObjectsResolveResponse['alias_target_id'] | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is 'aliasMatch' or 'conflict'. | | [outcome](./kibana-plugin-core-public.resolvedsimplesavedobject.outcome.md) | SavedObjectsResolveResponse['outcome'] | The outcome for a successful resolve call is one of the following values:\* 'exactMatch' -- One document exactly matched the given ID. \* '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. | -| [savedObject](./kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md) | SimpleSavedObject<T> | The saved object that was found. | +| [saved\_object](./kibana-plugin-core-public.resolvedsimplesavedobject.saved_object.md) | SimpleSavedObject<T> | The saved object that was found. | diff --git a/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.saved_object.md similarity index 55% rename from docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md rename to docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.saved_object.md index c05e8801768c9..7d90791a26fd8 100644 --- a/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.savedobject.md +++ b/docs/development/core/public/kibana-plugin-core-public.resolvedsimplesavedobject.saved_object.md @@ -1,13 +1,13 @@ -[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. Signature: ```typescript -savedObject: SimpleSavedObject; +saved_object: SimpleSavedObject; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.alias_target_id.md similarity index 62% rename from docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md rename to docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.alias_target_id.md index 02055da686880..07c55ae922363 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.alias_target_id.md @@ -1,13 +1,13 @@ -[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'`. Signature: ```typescript -aliasTargetId?: string; +alias_target_id?: string; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.md b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.md index 4345f2949d48e..cdc79d8ac363d 100644 --- a/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.md +++ b/docs/development/core/public/kibana-plugin-core-public.savedobjectsresolveresponse.md @@ -15,7 +15,7 @@ export interface SavedObjectsResolveResponse | Property | Type | Description | | --- | --- | --- | -| [aliasTargetId](./kibana-plugin-core-public.savedobjectsresolveresponse.aliastargetid.md) | string | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is 'aliasMatch' or 'conflict'. | +| [alias\_target\_id](./kibana-plugin-core-public.savedobjectsresolveresponse.alias_target_id.md) | string | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is 'aliasMatch' or 'conflict'. | | [outcome](./kibana-plugin-core-public.savedobjectsresolveresponse.outcome.md) | 'exactMatch' | 'aliasMatch' | 'conflict' | The outcome for a successful resolve call is one of the following values:\* 'exactMatch' -- One document exactly matched the given ID. \* '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. | | [saved\_object](./kibana-plugin-core-public.savedobjectsresolveresponse.saved_object.md) | SavedObject<T> | The saved object that was found. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.aliastargetid.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.alias_target_id.md similarity index 62% rename from docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.aliastargetid.md rename to docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.alias_target_id.md index 2e73d6ba2e1a9..4e8bc5e787ede 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.aliastargetid.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.alias_target_id.md @@ -1,13 +1,13 @@ -[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'`. Signature: ```typescript -aliasTargetId?: string; +alias_target_id?: string; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md index 8a2504ec7adcc..bbffd9902c0e7 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsresolveresponse.md @@ -15,7 +15,7 @@ export interface SavedObjectsResolveResponse | Property | Type | Description | | --- | --- | --- | -| [aliasTargetId](./kibana-plugin-core-server.savedobjectsresolveresponse.aliastargetid.md) | string | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is 'aliasMatch' or 'conflict'. | +| [alias\_target\_id](./kibana-plugin-core-server.savedobjectsresolveresponse.alias_target_id.md) | string | The ID of the object that the legacy URL alias points to. This is only defined when the outcome is 'aliasMatch' or 'conflict'. | | [outcome](./kibana-plugin-core-server.savedobjectsresolveresponse.outcome.md) | 'exactMatch' | 'aliasMatch' | 'conflict' | The outcome for a successful resolve call is one of the following values:\* 'exactMatch' -- One document exactly matched the given ID. \* '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. | | [saved\_object](./kibana-plugin-core-server.savedobjectsresolveresponse.saved_object.md) | SavedObject<T> | The saved object that was found. | diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 9de10c3c88534..5ce12a1889c26 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1149,9 +1149,9 @@ export type ResolveDeprecationResponse = { // @public export interface ResolvedSimpleSavedObject { - aliasTargetId?: SavedObjectsResolveResponse['aliasTargetId']; + alias_target_id?: SavedObjectsResolveResponse['alias_target_id']; outcome: SavedObjectsResolveResponse['outcome']; - savedObject: SimpleSavedObject; + saved_object: SimpleSavedObject; } // 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 { - aliasTargetId?: string; + alias_target_id?: string; outcome: 'exactMatch' | 'aliasMatch' | 'conflict'; saved_object: SavedObject; } diff --git a/src/core/public/saved_objects/saved_objects_client.test.ts b/src/core/public/saved_objects/saved_objects_client.test.ts index 85441b9841eaf..2eed9615430e9 100644 --- a/src/core/public/saved_objects/saved_objects_client.test.ts +++ b/src/core/public/saved_objects/saved_objects_client.test.ts @@ -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'); }); }); diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index 838b7adebc897..0b15a6f8961cd 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -442,9 +442,13 @@ export class SavedObjectsClient { const path = `${this.getPath(['resolve'])}/${type}/${id}`; const request: Promise> = this.savedObjectsFetch(path, {}); - return request.then(({ saved_object: object, outcome, aliasTargetId }) => { - const savedObject = new SimpleSavedObject(this, object); - return { savedObject, outcome, aliasTargetId }; + return request.then((resolveResponse) => { + const simpleSavedObject = new SimpleSavedObject(this, resolveResponse.saved_object); + return { + saved_object: simpleSavedObject, + outcome: resolveResponse.outcome, + alias_target_id: resolveResponse.alias_target_id, + }; }); }; diff --git a/src/core/public/saved_objects/types.ts b/src/core/public/saved_objects/types.ts index ac3df16730125..1251e75b5d6e2 100644 --- a/src/core/public/saved_objects/types.ts +++ b/src/core/public/saved_objects/types.ts @@ -19,7 +19,7 @@ export interface ResolvedSimpleSavedObject { /** * The saved object that was found. */ - savedObject: SimpleSavedObject; + saved_object: SimpleSavedObject; /** * The outcome for a successful `resolve` call is one of the following values: * @@ -33,5 +33,5 @@ export interface ResolvedSimpleSavedObject { /** * 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']; } diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index c025adce29808..eead42db1ec58 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -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, }); }); }); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 7ac4fe87bfc19..17b0f10ef67c8 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -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; } diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index abb86d8120a9b..00d47d8d1fb03 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -328,7 +328,7 @@ export interface SavedObjectsResolveResponse { /** * 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; } /** diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 985e0e337d75c..b18479af23bb2 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -3029,7 +3029,7 @@ export interface SavedObjectsResolveImportErrorsOptions { // @public (undocumented) export interface SavedObjectsResolveResponse { - aliasTargetId?: string; + alias_target_id?: string; outcome: 'exactMatch' | 'aliasMatch' | 'conflict'; saved_object: SavedObject; } diff --git a/src/plugins/spaces_oss/public/api.ts b/src/plugins/spaces_oss/public/api.ts index b1b6a16958dbd..7492142f0d792 100644 --- a/src/plugins/spaces_oss/public/api.ts +++ b/src/plugins/spaces_oss/public/api.ts @@ -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`. * diff --git a/x-pack/test/saved_object_api_integration/common/suites/resolve.ts b/x-pack/test/saved_object_api_integration/common/suites/resolve.ts index 47aafc400ce76..bfaeff7f366a4 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/resolve.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/resolve.ts @@ -85,9 +85,9 @@ export function resolveTestSuiteFactory(esArchiver: any, supertest: SuperTest