Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate away from browser-side SO client #149098

Closed
jloleysens opened this issue Jan 18, 2023 · 4 comments
Closed

Migrate away from browser-side SO client #149098

jloleysens opened this issue Jan 18, 2023 · 4 comments
Labels
Feature:Saved Objects Meta Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc

Comments

@jloleysens
Copy link
Contributor

jloleysens commented Jan 18, 2023

Summary

The browser-side implementation of Saved Object (SO) client is being deprecated as part of removing public references to saved object schemas (persistence schemas).

Note to Elastic developers

See this issue.

Requirements per use case

CRUD (including bulk)

The SO client is a convenient shortcut to CRUD operations on any SO type. Consider this usage of a .create:

// browser-side only
await soClient.create('my-type', { ...values }, { ...soOptionsLikeReferences });

must be replaced with the equivalent of:

// common, here "Foo" is stored in the 'my-type' SO, but that is treated
// as an implementation detail from the browser's perspective
interface Foo {
  name: string;
}

// browser-side, notice public only knows about `Foo` interface,
// not the entire SO attributes (even though they may be the same)
await http.post('/api/my-app/my-type/create', { body: JSON.stringify({ ...allValues } as Foo) })

// server-side
router.post(
  {
    path: '/api/my-app/my-type/create',
    validate: { body: schema.object({ name: schema.string() }) },
  },
  async (context, request, response) => {
    const { id } = await savedObjects.create(
      'my-type',
      { ...body.values },
      { ...body.yourReferences }
    );
    return response.ok({ id });
  }
);

Querying and Aggs

.find directly exposes the full ES API to browser-side clients. This is problematic because you need to work directly with type.attribute.my_field style APIs to use it.

// browser-side only
  const response = await savedObjectsClient.find({
    type: 'my-type',
    perPage: 10,
    search: `my-type.name: "${title}"`,
  });

must be replaced with the equivalent of:

// common
interface Foo {
  name: string;
}

interface GetFooResponseHTTP {
  foos: Foo[];
}

// browser-side, notice public only knows about `Foo` interface,
// not the entire SO attributes (even though they may be the same)
await http.get<GetFooResponseHTTP>(
 '/api/my-app/find-foos',
  { query: { name: 'something', /* 🐍 case for query params */ per_page: 10 } }
)

// server-side
import { escapeKuery } from '@kbn/es-query';
router.get(
  {
    path: '/api/my-app/find-foos',
    validate: {
      query: schema.object({
        name: schema.oneOf([schema.literal('foo'), schema.literal('bar')]),
        per_page: schema.number({ min: 1, defaultValue: 100 }),
      }),
    },
  },
  async (context, request, response) => {
    const result = await savedObjects.find({
      type: 'my-type',
      perPage: request.query.per_page,
      search: `my-type.name: ${escapeKuery(request.query.name)}`,
    });
    // Assuming that, for now, your Foo interface your SO interface are exactly the same...
    return response.ok({ foos: result.saved_objects.map(({ attributes })  => ({ ... attributes }));
  }
);

Resolve

As part of the multiple spaces readiness migration for 8.0, plugins using saved object id's that could originate from external systems (user input, bookmarks) had to switch to using the resolve API. This ensures that 7.x saved object ids can be "resolved" to 8.x ids, optionally giving users a warning to update old links etc.

.resolve returns the whole document and this API is therefore not backwards compatible.

While each use-case should be considered very carefully, in general, apps should send the old ID to the server and use .resolve() on the server. your endpoint should accept the old ID and use .resolve() on the server.

// browser-side only
  const response = await savedObjectsClient.resolve({
    type: 'my-type',
    id: 'my-old-object-id',
  });

could be replaced with the equivalent of:

// common
interface Foo {
  name: string;
}

// For your own resolve endpoint you can choose to directly use the SO types or define your own
// mapped from the SO resolve response types.
// In this example we will use the SO resolve response directly:
import type { SavedObjectsResolveResponse } from '@kbn/core-saved-objects-api-server';
interface ResolveFooResponseHTTP {
  foo: Foo;
  outcome: SavedObjectsResolveResponse['outcome'];
  alias_target_id?: SavedObjectsResolveResponse['alias_target_id'];
  alias_purpose?: SavedObjectsResolveResponse['outcome'];
}

// browser-side
const { foo, outcome, alias_purpose, alias_target_id } = await http.get<ResolveFooResponseHTTP>('/api/my-app/my-type/resolve/{id}',
  { params: { id: 'my-old-object-id' } }
)
// Now you can use the full functionality of the resolve response as before

// server-side
router.get(
  {
    path: '/api/my-app/resolve-foo',
    validate: {
      params: schema.string(),
    },
  },
  async (context, request, response) => {
    const resolveResult = await savedObjects.resolve({
      type: 'my-type',
      id: 'my-old-object-id'
    });
    const { saved_object, outcome, alias_target_id, alias_purpose } = resolveResult;
    // Explicitly map your SO-attribs to your response
    return response.ok({ foo: { name: saved_object.name }, outcome, alias_target_id, alias_purpose  });
  }
);

Be careful to not leak SO-specific interface details to clients when using this API.

Resources

CC @rudolf @TinaHeiligers

@jloleysens jloleysens added Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Meta Feature:Saved Objects labels Jan 18, 2023
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-core (Team:Core)

@pgayvallet
Copy link
Contributor

@jloleysens shouldn't this be closed by #148979?

@jloleysens
Copy link
Contributor Author

@pgayvallet We want to use this public issue as a way to share instructions for migrating away from browser-side SO usage. #148979 just adds a deprecation in the code, perhaps this should be renamed :)

@jloleysens jloleysens changed the title Kibana saved object client deprecation Migrate away from browser-side SO client Jan 24, 2023
jloleysens added a commit that referenced this issue Feb 2, 2023
## Summary

Part of preparing HTTP APIs and associated interfaces for versioning:

* Add domain-specific interfaces to the saved object management plugin
    * Add a V1 interface of domain types to `common`
    * Remove use of deprecated `SavedObject` type from public
* Follows on from #148602


Related #149098

Fixes #149495

---------

Co-authored-by: kibanamachine <[email protected]>
@pgayvallet
Copy link
Contributor

@TinaHeiligers I think this is superseded by the 9.0 SO API deprecation, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Saved Objects Meta Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc
Projects
None yet
Development

No branches or pull requests

4 participants