Skip to content

Commit

Permalink
[Saved objects] Prepare SO management for versionable types (#149495)
Browse files Browse the repository at this point in the history
## 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 <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
jloleysens and kibanamachine authored Feb 2, 2023
1 parent b161749 commit df809bd
Show file tree
Hide file tree
Showing 35 changed files with 383 additions and 211 deletions.
2 changes: 1 addition & 1 deletion src/plugins/saved_objects_management/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ export type {
SavedObjectRelation,
SavedObjectRelationKind,
SavedObjectInvalidRelation,
SavedObjectGetRelationshipsResponse,
SavedObjectManagementTypeInfo,
v1,
} from './types';
61 changes: 0 additions & 61 deletions src/plugins/saved_objects_management/common/types.ts

This file was deleted.

58 changes: 58 additions & 0 deletions src/plugins/saved_objects_management/common/types/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
## Versioned interfaces

This folder contains types that are shared between the server and client:

```ts
// v1.ts
export interface SavedObjectWithMetadata { name: string }

// index.ts
import * as v1 from './v1';
export type { v1 };

// Used elsewhere
import type { v1 } from '../common';
const myObject: v1.SavedObjectWithMetadata = { name: 'my object' };
```

**Do not alter a versioned type**. Types may be in use by clients (if the code is released).
Alterations must be made on a new version of the TS interface.

## Create a new version

Versions in this plugin are determined using monotonically increasing numbers: 1, 2, 3, etc.

1. Find the latest version, e.g: `v2`.
2. Create a new file, e.g., `v3.ts` if it does not exist.
3. Copy the type(s) to change from previous version. E.g. `v2.ts`'s `SavedObjectWithMetadata`.
4. Alter the interface as needed
5. Re-export `v2` types to "inherit" the entire previous version's types: `export * from './v2';`
6. Export your new version from latest: `export * from './v3';`. This may result in TS errors
to be fixed.
7. Export your new file from index.ts as `v3`.

Your `v3.ts` file should look something like:

```ts
export * from './v3';
export interface SavedObjectWithMetadata { name: string; a_new_field: string; }
```

In this way the entire API is accessible from `v3` including types that may
not have changed.

Any alterations post-release must be in a new version (start at step 1).


## The `latest.ts` file

The `latest.ts` file is a container for all "latest" versions of types. This is useful
for app code that always needs the latest version of your interfaces. E.g.:

```ts
import type { SavedObjectWithMetadata } from '../common';
```

Notice that there is no version number mentioned. Either in the interface name
or import path. To update the "latest" type you must re-export the new version
from the appropriate versioned path.
19 changes: 19 additions & 0 deletions src/plugins/saved_objects_management/common/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export type {
SavedObjectInvalidRelation,
SavedObjectManagementTypeInfo,
SavedObjectMetadata,
SavedObjectRelation,
SavedObjectRelationKind,
SavedObjectWithMetadata,
} from './latest';

import type * as v1 from './v1';
export type { v1 };
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,4 @@
* Side Public License, v 1.
*/

import { mapKeys, camelCase } from 'lodash';

export function keysToCamelCaseShallow(object: Record<string, any>) {
return mapKeys(object, (value, key) => camelCase(key));
}
export * from './v1';
178 changes: 178 additions & 0 deletions src/plugins/saved_objects_management/common/types/v1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { SavedObjectError } from '@kbn/core/types';
import type { SavedObjectsNamespaceType } from '@kbn/core/public';

/** Domain interfaces */

/**
* Saved Object Management metadata associated with a saved object. See
* {@link SavedObjectWithMetadata}.
*/
export interface SavedObjectMetadata {
icon?: string;
title?: string;
editUrl?: string;
inAppUrl?: { path: string; uiCapabilitiesPath: string };
namespaceType?: SavedObjectsNamespaceType;
hiddenType?: boolean;
}

/**
* One saved object's reference to another saved object.
*/
export interface SavedObjectReference {
name: string;
type: string;
id: string;
}

/**
* A saved object.
*
* @note This is intended as a domain-specific representation of a SavedObject
* which is intended for server-side only use.
*/
export interface SavedObjectWithMetadata<T = unknown> {
id: string;
type: string;
meta: SavedObjectMetadata;
error?: SavedObjectError;
created_at?: string;
updated_at?: string;
attributes: T;
namespaces?: string[];
references: SavedObjectReference[];
}

export type SavedObjectRelationKind = 'child' | 'parent';

/**
* Represents a relation between two {@link SavedObjectWithMetadata | saved objects}.
*/
export interface SavedObjectRelation {
id: string;
type: string;
relationship: SavedObjectRelationKind;
meta: SavedObjectMetadata;
}

/**
* Represents a relation between two {@link SavedObjectWithMetadata | saved objects}.
*/
export interface SavedObjectInvalidRelation {
id: string;
type: string;
relationship: SavedObjectRelationKind;
error: string;
}

export interface SavedObjectManagementTypeInfo {
name: string;
// TODO: Fix. We should not directly expose these values to public code.
namespaceType: SavedObjectsNamespaceType;
hidden: boolean;
displayName: string;
}

/** HTTP API interfaces */

export type BulkGetBodyHTTP = Array<{
id: string;
type: string;
}>;

export type BulkGetResponseHTTP = SavedObjectWithMetadata[];

export type BulkDeleteBodyHTTP = Array<{
type: string;
id: string;
}>;

export type BulkDeleteResponseHTTP = Array<{
/** The ID of the saved object */
id: string;
/** The type of the saved object */
type: string;
/** The status of deleting the object: true for deleted, false for error */
success: boolean;
/** Reason the object could not be deleted (success is false) */
error?: SavedObjectError;
}>;

export type FindSearchOperatorHTTP = 'AND' | 'OR';
export type FindSortOrderHTTP = 'asc' | 'desc';

export interface ReferenceHTTP {
type: string;
id: string;
}

export interface FindQueryHTTP {
perPage?: number;
page?: number;
type: string | string[];
// TODO: Fix. this API allows writing an arbitrary query that is passed straight to our persistence layer, thus leaking SO attributes to the public...
search?: string;
defaultSearchOperator?: FindSearchOperatorHTTP;
// TODO: Fix. this API allows sorting by any field, thus leaking SO attributes to the public...
sortField?: string;
sortOrder?: FindSortOrderHTTP;
hasReference?: ReferenceHTTP | ReferenceHTTP[];
hasReferenceOperator?: FindSearchOperatorHTTP;
// TODO: Fix. This exposes attribute schemas to clients.
fields?: string | string[];
}

export interface FindResponseHTTP {
saved_objects: SavedObjectWithMetadata[];
total: number;
page: number;
per_page: number;
}

export interface GetAllowedTypesResponseHTTP {
types: SavedObjectManagementTypeInfo[];
}

export interface RelationshipsParamsHTTP {
type: string;
id: string;
}

export interface RelationshipsQueryHTTP {
size: number;
savedObjectTypes: string | string[];
}

export interface RelationshipsResponseHTTP {
relations: SavedObjectRelation[];
invalidRelations: SavedObjectInvalidRelation[];
}

export interface ScrollCountBodyHTTP {
typesToInclude: string[];
// TODO: Fix. this API allows writing an arbitrary query that is passed straight to our persistence layer, thus leaking SO attributes to the public...
searchString?: string;
references?: Array<{ type: string; id: string }>;
}

export interface DeleteObjectBodyHTTP {
id: string;
type: string;
}

export interface DeleteObjectResponseHTTP {
id: string;
}

/**
* In this case "string" is a direct mapping from "typesToInlcude" in {@link ScrollCountBodyHTTP['typesToInclude']']}
*/
export type ScrollCountResponseHTTP = Record<string, number>;
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,14 @@
* Side Public License, v 1.
*/

import { HttpStart } from '@kbn/core/public';
import { SavedObjectError, SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common';

interface SavedObjectDeleteStatus {
id: string;
success: boolean;
type: string;
error?: SavedObjectError;
}
import type { HttpStart } from '@kbn/core/public';
import type { v1 } from '../../common';

export function bulkDeleteObjects(
http: HttpStart,
objects: SavedObjectTypeIdTuple[]
): Promise<SavedObjectDeleteStatus[]> {
return http.post<SavedObjectDeleteStatus[]>(
'/internal/kibana/management/saved_objects/_bulk_delete',
{
body: JSON.stringify(objects),
}
);
objects: v1.BulkDeleteBodyHTTP
): Promise<v1.BulkDeleteResponseHTTP> {
return http.post('/internal/kibana/management/saved_objects/_bulk_delete', {
body: JSON.stringify(objects),
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
* Side Public License, v 1.
*/

import { HttpStart } from '@kbn/core/public';
import { SavedObjectWithMetadata } from '../types';
import type { HttpStart } from '@kbn/core/public';
import type { v1 } from '../../common';

export async function bulkGetObjects(
http: HttpStart,
objects: Array<{ type: string; id: string }>
): Promise<SavedObjectWithMetadata[]> {
return await http.post<SavedObjectWithMetadata[]>(
`/api/kibana/management/saved_objects/_bulk_get`,
{ body: JSON.stringify(objects) }
);
objects: v1.BulkGetBodyHTTP
): Promise<v1.BulkGetResponseHTTP> {
return await http.post(`/api/kibana/management/saved_objects/_bulk_get`, {
body: JSON.stringify(objects),
});
}
Loading

0 comments on commit df809bd

Please sign in to comment.