Skip to content

Commit

Permalink
Add SavedObjectsUtils.getConvertedObjectId function (elastic#107767)
Browse files Browse the repository at this point in the history
  • Loading branch information
jportner authored and vadimkibana committed Aug 8, 2021
1 parent a57624e commit 21b65cf
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [SavedObjectsUtils](./kibana-plugin-core-server.savedobjectsutils.md) &gt; [getConvertedObjectId](./kibana-plugin-core-server.savedobjectsutils.getconvertedobjectid.md)

## SavedObjectsUtils.getConvertedObjectId() method

Uses a single-namespace object's "legacy ID" to determine what its new ID will be after it is converted to a multi-namespace type.

<b>Signature:</b>

```typescript
static getConvertedObjectId(namespace: string | undefined, type: string, id: string): string;
```

## Parameters

| Parameter | Type | Description |
| --- | --- | --- |
| namespace | <code>string &#124; undefined</code> | |
| type | <code>string</code> | |
| id | <code>string</code> | |

<b>Returns:</b>

`string`

{<!-- -->string<!-- -->} The ID of the saved object after it is converted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ export declare class SavedObjectsUtils
| Method | Modifiers | Description |
| --- | --- | --- |
| [generateId()](./kibana-plugin-core-server.savedobjectsutils.generateid.md) | <code>static</code> | Generates a random ID for a saved objects. |
| [getConvertedObjectId(namespace, type, id)](./kibana-plugin-core-server.savedobjectsutils.getconvertedobjectid.md) | <code>static</code> | Uses a single-namespace object's "legacy ID" to determine what its new ID will be after it is converted to a multi-namespace type. |
| [isRandomId(id)](./kibana-plugin-core-server.savedobjectsutils.israndomid.md) | <code>static</code> | Validates that a saved object ID has been randomly generated. |

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.
*/

const mockGetConvertedObjectId = jest.fn().mockReturnValue('uuidv5');
jest.mock('../../service/lib/utils', () => {
const actual = jest.requireActual('../../service/lib/utils');
return {
...actual,
SavedObjectsUtils: {
...actual.SavedObjectsUtils,
getConvertedObjectId: mockGetConvertedObjectId,
},
};
});

export { mockGetConvertedObjectId };
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Side Public License, v 1.
*/

import { mockUuidv5 } from './__mocks__';
import { mockGetConvertedObjectId } from './document_migrator.test.mock';
import { set } from '@elastic/safer-lodash-set';
import _ from 'lodash';
import { SavedObjectUnsanitizedDoc } from '../../serialization';
Expand Down Expand Up @@ -37,7 +37,7 @@ const createRegistry = (...types: Array<Partial<SavedObjectsType>>) => {
};

beforeEach(() => {
mockUuidv5.mockClear();
mockGetConvertedObjectId.mockClear();
});

describe('DocumentMigrator', () => {
Expand Down Expand Up @@ -866,7 +866,7 @@ describe('DocumentMigrator', () => {
namespace: 'foo-namespace',
};
const actual = migrator.migrate(obj);
expect(mockUuidv5).not.toHaveBeenCalled();
expect(mockGetConvertedObjectId).not.toHaveBeenCalled();
expect(actual).toEqual({
id: 'cowardly',
type: 'dog',
Expand Down Expand Up @@ -898,7 +898,7 @@ describe('DocumentMigrator', () => {

it('in the default space', () => {
const actual = migrator.migrateAndConvert(obj);
expect(mockUuidv5).not.toHaveBeenCalled();
expect(mockGetConvertedObjectId).not.toHaveBeenCalled();
expect(actual).toEqual([
{
id: 'bad',
Expand All @@ -912,8 +912,8 @@ describe('DocumentMigrator', () => {

it('in a non-default space', () => {
const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' });
expect(mockUuidv5).toHaveBeenCalledTimes(1);
expect(mockUuidv5).toHaveBeenCalledWith('foo-namespace:toy:favorite', 'DNSUUID');
expect(mockGetConvertedObjectId).toHaveBeenCalledTimes(1);
expect(mockGetConvertedObjectId).toHaveBeenCalledWith('foo-namespace', 'toy', 'favorite');
expect(actual).toEqual([
{
id: 'bad',
Expand Down Expand Up @@ -946,7 +946,7 @@ describe('DocumentMigrator', () => {

it('in the default space', () => {
const actual = migrator.migrateAndConvert(obj);
expect(mockUuidv5).not.toHaveBeenCalled();
expect(mockGetConvertedObjectId).not.toHaveBeenCalled();
expect(actual).toEqual([
{
id: 'loud',
Expand All @@ -961,8 +961,8 @@ describe('DocumentMigrator', () => {

it('in a non-default space', () => {
const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' });
expect(mockUuidv5).toHaveBeenCalledTimes(1);
expect(mockUuidv5).toHaveBeenCalledWith('foo-namespace:dog:loud', 'DNSUUID');
expect(mockGetConvertedObjectId).toHaveBeenCalledTimes(1);
expect(mockGetConvertedObjectId).toHaveBeenCalledWith('foo-namespace', 'dog', 'loud');
expect(actual).toEqual([
{
id: 'uuidv5',
Expand Down Expand Up @@ -1008,7 +1008,7 @@ describe('DocumentMigrator', () => {

it('in the default space', () => {
const actual = migrator.migrateAndConvert(obj);
expect(mockUuidv5).not.toHaveBeenCalled();
expect(mockGetConvertedObjectId).not.toHaveBeenCalled();
expect(actual).toEqual([
{
id: 'cute',
Expand All @@ -1024,9 +1024,19 @@ describe('DocumentMigrator', () => {

it('in a non-default space', () => {
const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' });
expect(mockUuidv5).toHaveBeenCalledTimes(2);
expect(mockUuidv5).toHaveBeenNthCalledWith(1, 'foo-namespace:toy:favorite', 'DNSUUID');
expect(mockUuidv5).toHaveBeenNthCalledWith(2, 'foo-namespace:dog:cute', 'DNSUUID');
expect(mockGetConvertedObjectId).toHaveBeenCalledTimes(2);
expect(mockGetConvertedObjectId).toHaveBeenNthCalledWith(
1,
'foo-namespace',
'toy',
'favorite'
);
expect(mockGetConvertedObjectId).toHaveBeenNthCalledWith(
2,
'foo-namespace',
'dog',
'cute'
);
expect(actual).toEqual([
{
id: 'uuidv5',
Expand Down Expand Up @@ -1080,7 +1090,7 @@ describe('DocumentMigrator', () => {

it('in the default space', () => {
const actual = migrator.migrateAndConvert(obj);
expect(mockUuidv5).not.toHaveBeenCalled();
expect(mockGetConvertedObjectId).not.toHaveBeenCalled();
expect(actual).toEqual([
{
id: 'sleepy',
Expand All @@ -1095,8 +1105,8 @@ describe('DocumentMigrator', () => {

it('in a non-default space', () => {
const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' });
expect(mockUuidv5).toHaveBeenCalledTimes(1);
expect(mockUuidv5).toHaveBeenCalledWith('foo-namespace:toy:favorite', 'DNSUUID');
expect(mockGetConvertedObjectId).toHaveBeenCalledTimes(1);
expect(mockGetConvertedObjectId).toHaveBeenCalledWith('foo-namespace', 'toy', 'favorite');
expect(actual).toEqual([
{
id: 'sleepy',
Expand Down Expand Up @@ -1134,7 +1144,7 @@ describe('DocumentMigrator', () => {

it('in the default space', () => {
const actual = migrator.migrateAndConvert(obj);
expect(mockUuidv5).not.toHaveBeenCalled();
expect(mockGetConvertedObjectId).not.toHaveBeenCalled();
expect(actual).toEqual([
{
id: 'hungry',
Expand All @@ -1149,8 +1159,8 @@ describe('DocumentMigrator', () => {

it('in a non-default space', () => {
const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' });
expect(mockUuidv5).toHaveBeenCalledTimes(1);
expect(mockUuidv5).toHaveBeenCalledWith('foo-namespace:dog:hungry', 'DNSUUID');
expect(mockGetConvertedObjectId).toHaveBeenCalledTimes(1);
expect(mockGetConvertedObjectId).toHaveBeenCalledWith('foo-namespace', 'dog', 'hungry');
expect(actual).toEqual([
{
id: 'uuidv5',
Expand Down Expand Up @@ -1204,7 +1214,7 @@ describe('DocumentMigrator', () => {

it('in the default space', () => {
const actual = migrator.migrateAndConvert(obj);
expect(mockUuidv5).not.toHaveBeenCalled();
expect(mockGetConvertedObjectId).not.toHaveBeenCalled();
expect(actual).toEqual([
{
id: 'pretty',
Expand All @@ -1220,9 +1230,19 @@ describe('DocumentMigrator', () => {

it('in a non-default space', () => {
const actual = migrator.migrateAndConvert({ ...obj, namespace: 'foo-namespace' });
expect(mockUuidv5).toHaveBeenCalledTimes(2);
expect(mockUuidv5).toHaveBeenNthCalledWith(1, 'foo-namespace:toy:favorite', 'DNSUUID');
expect(mockUuidv5).toHaveBeenNthCalledWith(2, 'foo-namespace:dog:pretty', 'DNSUUID');
expect(mockGetConvertedObjectId).toHaveBeenCalledTimes(2);
expect(mockGetConvertedObjectId).toHaveBeenNthCalledWith(
1,
'foo-namespace',
'toy',
'favorite'
);
expect(mockGetConvertedObjectId).toHaveBeenNthCalledWith(
2,
'foo-namespace',
'dog',
'pretty'
);
expect(actual).toEqual([
{
id: 'uuidv5',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import { MigrationLogger } from './migration_logger';
import { TransformSavedObjectDocumentError } from '.';
import { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry';
import { SavedObjectMigrationFn, SavedObjectMigrationMap } from '../types';
import { DEFAULT_NAMESPACE_STRING } from '../../service/lib/utils';
import { DEFAULT_NAMESPACE_STRING, SavedObjectsUtils } from '../../service/lib/utils';
import { LegacyUrlAlias, LEGACY_URL_ALIAS_TYPE } from '../../object_types';

const DEFAULT_MINIMUM_CONVERT_VERSION = '8.0.0';
Expand Down Expand Up @@ -556,7 +556,7 @@ function convertNamespaceType(doc: SavedObjectUnsanitizedDoc) {
}

const { id: originId, type } = otherAttrs;
const id = deterministicallyRegenerateObjectId(namespace, type, originId!);
const id = SavedObjectsUtils.getConvertedObjectId(namespace, type, originId!);
if (namespace !== undefined) {
const legacyUrlAlias: SavedObjectUnsanitizedDoc<LegacyUrlAlias> = {
id: `${namespace}:${type}:${originId}`,
Expand Down Expand Up @@ -616,7 +616,9 @@ function getReferenceTransforms(typeRegistry: ISavedObjectTypeRegistry): Transfo
references: references.map(({ type, id, ...attrs }) => ({
...attrs,
type,
id: types.has(type) ? deterministicallyRegenerateObjectId(namespace, type, id) : id,
id: types.has(type)
? SavedObjectsUtils.getConvertedObjectId(namespace, type, id)
: id,
})),
},
additionalDocs: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
* Side Public License, v 1.
*/

const mockUuidv1 = jest.fn().mockReturnValue('uuidv1');
jest.mock('uuid/v1', () => mockUuidv1);

const mockUuidv5 = jest.fn().mockReturnValue('uuidv5');
Object.defineProperty(mockUuidv5, 'DNS', { value: 'DNSUUID', writable: false });
jest.mock('uuid/v5', () => mockUuidv5);

export { mockUuidv5 };
export { mockUuidv1, mockUuidv5 };
28 changes: 21 additions & 7 deletions src/core/server/saved_objects/service/lib/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,19 @@
* Side Public License, v 1.
*/

import uuid from 'uuid';
import { mockUuidv1, mockUuidv5 } from './utils.test.mock';

import { SavedObjectsFindOptions } from '../../types';
import { SavedObjectsUtils } from './utils';

jest.mock('uuid', () => ({
v1: jest.fn().mockReturnValue('mock-uuid'),
}));

describe('SavedObjectsUtils', () => {
const {
namespaceIdToString,
namespaceStringToId,
createEmptyFindResponse,
generateId,
isRandomId,
getConvertedObjectId,
} = SavedObjectsUtils;

describe('#namespaceIdToString', () => {
Expand Down Expand Up @@ -80,8 +78,8 @@ describe('SavedObjectsUtils', () => {

describe('#generateId', () => {
it('returns a valid uuid', () => {
expect(generateId()).toBe('mock-uuid');
expect(uuid.v1).toHaveBeenCalled();
expect(generateId()).toBe('uuidv1'); // default return value for mockUuidv1
expect(mockUuidv1).toHaveBeenCalled();
});
});

Expand All @@ -93,4 +91,20 @@ describe('SavedObjectsUtils', () => {
expect(isRandomId(undefined)).toBe(false);
});
});

describe('#getConvertedObjectId', () => {
it('does not change the object ID if namespace is undefined or "default"', () => {
for (const namespace of [undefined, 'default']) {
const result = getConvertedObjectId(namespace, 'type', 'oldId');
expect(result).toBe('oldId');
expect(mockUuidv5).not.toHaveBeenCalled();
}
});

it('changes the object ID if namespace is defined and not "default"', () => {
const result = getConvertedObjectId('namespace', 'type', 'oldId');
expect(result).toBe('uuidv5'); // default return value for mockUuidv5
expect(mockUuidv5).toHaveBeenCalledWith('namespace:type:oldId', 'DNSUUID');
});
});
});
20 changes: 18 additions & 2 deletions src/core/server/saved_objects/service/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* Side Public License, v 1.
*/

import uuid from 'uuid';
import uuidv1 from 'uuid/v1';
import uuidv5 from 'uuid/v5';
import { SavedObjectsFindOptions } from '../../types';
import { SavedObjectsFindResponse } from '..';

Expand Down Expand Up @@ -65,7 +66,7 @@ export class SavedObjectsUtils {
* Generates a random ID for a saved objects.
*/
public static generateId() {
return uuid.v1();
return uuidv1();
}

/**
Expand All @@ -77,4 +78,19 @@ export class SavedObjectsUtils {
public static isRandomId(id: string | undefined) {
return typeof id === 'string' && UUID_REGEX.test(id);
}

/**
* Uses a single-namespace object's "legacy ID" to determine what its new ID will be after it is converted to a multi-namespace type.
*
* @param {string} namespace The namespace of the saved object before it is converted.
* @param {string} type The type of the saved object before it is converted.
* @param {string} id The ID of the saved object before it is converted.
* @returns {string} The ID of the saved object after it is converted.
*/
public static getConvertedObjectId(namespace: string | undefined, type: string, id: string) {
if (SavedObjectsUtils.namespaceIdToString(namespace) === DEFAULT_NAMESPACE_STRING) {
return id; // Objects that exist in the Default space do not get new IDs when they are converted.
}
return uuidv5(`${namespace}:${type}:${id}`, uuidv5.DNS); // The uuidv5 namespace constant (uuidv5.DNS) is arbitrary.
}
}
1 change: 1 addition & 0 deletions src/core/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3163,6 +3163,7 @@ export interface SavedObjectsUpdateResponse<T = unknown> extends Omit<SavedObjec
export class SavedObjectsUtils {
static createEmptyFindResponse: <T, A>({ page, perPage, }: SavedObjectsFindOptions) => SavedObjectsFindResponse<T, A>;
static generateId(): string;
static getConvertedObjectId(namespace: string | undefined, type: string, id: string): string;
static isRandomId(id: string | undefined): boolean;
static namespaceIdToString: (namespace?: string | undefined) => string;
static namespaceStringToId: (namespace: string) => string | undefined;
Expand Down

0 comments on commit 21b65cf

Please sign in to comment.