Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b427c20
STEP 1: refactor to not use the public SO HTTP APIs!
jloleysens Jan 13, 2023
3d1901d
STEP 2: Build a versionable type layer for your domain types AND your…
jloleysens Jan 13, 2023
849971c
body type -> query type
jloleysens Jan 13, 2023
b2374bd
update public code to use HTTP API interfaces
jloleysens Jan 13, 2023
adeb24e
added delete route types
jloleysens Jan 13, 2023
cf77557
actually register the delete route
jloleysens Jan 13, 2023
9962f8d
Merge branch 'main' into saved-objects-man-versioned-apis
jloleysens Jan 26, 2023
935ad21
remove unused route
jloleysens Jan 26, 2023
225ec1d
fix usage of bulk response v1
jloleysens Jan 26, 2023
04fe02d
update find related types
jloleysens Jan 26, 2023
0782ba8
remove dependence on public SavedObject type
jloleysens Jan 26, 2023
7ffd75a
added versioned types for bulk delete body and response
jloleysens Jan 26, 2023
c35b104
use bulk response type server side
jloleysens Jan 26, 2023
969507f
type the scroll body response
jloleysens Jan 26, 2023
8806c50
use the TS type keyword where appropriate in server routes
jloleysens Jan 26, 2023
bff8a0f
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jan 26, 2023
f9b9839
Merge branch 'main' into saved-objects-man-versioned-apis
jloleysens Jan 27, 2023
ba2df2e
Find API: savedObjects -> saved_objects
jloleysens Jan 27, 2023
d8bf1f9
fix another deprecated import and update the domain-specific types
jloleysens Jan 27, 2023
05c586e
only send the necessary values over the wire
jloleysens Jan 27, 2023
697f6ca
added per_page and page values
jloleysens Jan 27, 2023
c7e35d3
Merge branch 'main' into saved-objects-man-versioned-apis
jloleysens Jan 27, 2023
f9b5bcb
remove keys to camelcase conversion
jloleysens Jan 27, 2023
8f706f1
refactor savedObjects -> saved_objects in Jest test
jloleysens Jan 27, 2023
70967c9
consistent naming
jloleysens Jan 27, 2023
2b347fc
consistent naming; missed one
jloleysens Jan 27, 2023
f1074aa
simplify use of generics
jloleysens Jan 27, 2023
f05ac13
Merge branch 'main' into saved-objects-man-versioned-apis
jloleysens Jan 30, 2023
681d372
Merge branch 'main' into saved-objects-man-versioned-apis
jloleysens Jan 31, 2023
7db7f77
use v1 type container
jloleysens Jan 31, 2023
d950e25
finish refactor of using v1 container
jloleysens Jan 31, 2023
84ba11f
remove non-existent type
jloleysens Jan 31, 2023
88d03c1
use type keyword
jloleysens Jan 31, 2023
fba8964
use unknown instead of any
jloleysens Jan 31, 2023
4aa0459
remove use of non-existing type
jloleysens Jan 31, 2023
542b448
added README.md
jloleysens Jan 31, 2023
e23f91e
Merge branch 'main' into saved-objects-man-versioned-apis
jloleysens Feb 2, 2023
bf02a74
version all types together
jloleysens Feb 2, 2023
026665e
remove V1
jloleysens Feb 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Copy Markdown
Contributor

@TinaHeiligers TinaHeiligers Feb 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the types that don't have the V1 suffix not expected to change at all? I'd expect to see all the types and interfaces declared in this file to only be the ones that change.

Shared types/interfaces that we can guarantee will never change could live elsewhere, for example, in the main types file.

Keeping the versioned-types file as small as possible helps us monitor changes over time.

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.
Copy link
Copy Markdown
Contributor Author

@jloleysens jloleysens Jan 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments like this represent tech debt that needs to be addressed to be fully compatible with versioned HTTP APIs. Need to dig a bit deeper into current use cases of each of these so deferring to follow up PRs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of those comments are relatively old. I agree though, we should resolve as much tech debt as possible now.

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> {
Copy link
Copy Markdown
Contributor

@TinaHeiligers TinaHeiligers Feb 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we indicate these are for internal purposes only?

/* @internal */
export function bulkDeleteObjects(...

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