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

[Do not merge] optional workspace parameters unit test add #200

Closed
1 change: 1 addition & 0 deletions src/core/public/saved_objects/saved_objects_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ export class SavedObjectsClient {
filter: 'filter',
namespaces: 'namespaces',
preference: 'preference',
workspaces: 'workspaces',
};

const renamedQuery = renameKeys<SavedObjectsFindOptions, any>(renameMap, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export interface SavedObjectsExportOptions {
excludeExportDetails?: boolean;
/** optional namespace to override the namespace used by the savedObjectsClient. */
namespace?: string;
/** optional workspaces to override the workspaces used by the savedObjectsClient. */
workspaces?: string[];
}

/**
Expand Down Expand Up @@ -87,13 +89,15 @@ async function fetchObjectsToExport({
exportSizeLimit,
savedObjectsClient,
namespace,
workspaces,
}: {
objects?: SavedObjectsExportOptions['objects'];
types?: string[];
search?: string;
exportSizeLimit: number;
savedObjectsClient: SavedObjectsClientContract;
namespace?: string;
workspaces?: string[];
}) {
if ((types?.length ?? 0) > 0 && (objects?.length ?? 0) > 0) {
throw Boom.badRequest(`Can't specify both "types" and "objects" properties when exporting`);
Expand All @@ -105,7 +109,10 @@ async function fetchObjectsToExport({
if (typeof search === 'string') {
throw Boom.badRequest(`Can't specify both "search" and "objects" properties when exporting`);
}
const bulkGetResult = await savedObjectsClient.bulkGet(objects, { namespace });
const bulkGetResult = await savedObjectsClient.bulkGet(objects, {
namespace,
...(workspaces ? { workspaces } : {}),
});
const erroredObjects = bulkGetResult.saved_objects.filter((obj) => !!obj.error);
if (erroredObjects.length) {
const err = Boom.badRequest();
Expand All @@ -121,6 +128,7 @@ async function fetchObjectsToExport({
search,
perPage: exportSizeLimit,
namespaces: namespace ? [namespace] : undefined,
...(workspaces ? { workspaces } : {}),
});
if (findResponse.total > exportSizeLimit) {
throw Boom.badRequest(`Can't export more than ${exportSizeLimit} objects`);
Expand Down Expand Up @@ -153,6 +161,7 @@ export async function exportSavedObjectsToStream({
includeReferencesDeep = false,
excludeExportDetails = false,
namespace,
workspaces,
}: SavedObjectsExportOptions) {
const rootObjects = await fetchObjectsToExport({
types,
Expand All @@ -161,6 +170,7 @@ export async function exportSavedObjectsToStream({
savedObjectsClient,
exportSizeLimit,
namespace,
workspaces,
});
let exportedObjects: Array<SavedObject<unknown>> = [];
let missingReferences: SavedObjectsExportResultDetails['missingReferences'] = [];
Expand Down
3 changes: 3 additions & 0 deletions src/core/server/saved_objects/import/check_conflicts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ interface CheckConflictsParams {
ignoreRegularConflicts?: boolean;
retries?: SavedObjectsImportRetry[];
createNewCopies?: boolean;
workspaces?: string[];
}

const isUnresolvableConflict = (error: SavedObjectError) =>
Expand All @@ -56,6 +57,7 @@ export async function checkConflicts({
ignoreRegularConflicts,
retries = [],
createNewCopies,
workspaces,
}: CheckConflictsParams) {
const filteredObjects: Array<SavedObject<{ title?: string }>> = [];
const errors: SavedObjectsImportError[] = [];
Expand All @@ -77,6 +79,7 @@ export async function checkConflicts({
});
const checkConflictsResult = await savedObjectsClient.checkConflicts(objectsToCheck, {
namespace,
workspaces,
});
const errorMap = checkConflictsResult.errors.reduce(
(acc, { type, id, error }) => acc.set(`${type}:${id}`, error),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ interface CreateSavedObjectsParams<T> {
importIdMap: Map<string, { id?: string; omitOriginId?: boolean }>;
namespace?: string;
overwrite?: boolean;
workspaces?: string[];
}
interface CreateSavedObjectsResult<T> {
createdObjects: Array<CreatedObject<T>>;
Expand All @@ -56,6 +57,7 @@ export const createSavedObjects = async <T>({
importIdMap,
namespace,
overwrite,
workspaces,
}: CreateSavedObjectsParams<T>): Promise<CreateSavedObjectsResult<T>> => {
// filter out any objects that resulted in errors
const errorSet = accumulatedErrors.reduce(
Expand Down Expand Up @@ -103,14 +105,15 @@ export const createSavedObjects = async <T>({
const bulkCreateResponse = await savedObjectsClient.bulkCreate(objectsToCreate, {
namespace,
overwrite,
workspaces,
});
expectedResults = bulkCreateResponse.saved_objects;
}

// remap results to reflect the object IDs that were submitted for import
// this ensures that consumers understand the results
const remappedResults = expectedResults.map<CreatedObject<T>>((result) => {
const { id } = objectIdMap.get(`${result.type}:${result.id}`)!;
const { id } = objectIdMap.get(`${result.type}:${result.id}`) || ({} as SavedObject<T>);
// also, include a `destinationId` field if the object create attempt was made with a different ID
return { ...result, id, ...(id !== result.id && { destinationId: result.id }) };
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { typeRegistryMock } from '../saved_objects_type_registry.mock';
import { importSavedObjectsFromStream } from './import_saved_objects';

import { collectSavedObjects } from './collect_saved_objects';
import { regenerateIds } from './regenerate_ids';
import { regenerateIds, regenerateIdsWithReference } from './regenerate_ids';
import { validateReferences } from './validate_references';
import { checkConflicts } from './check_conflicts';
import { checkOriginConflicts } from './check_origin_conflicts';
Expand All @@ -68,6 +68,7 @@ describe('#importSavedObjectsFromStream', () => {
importIdMap: new Map(),
});
getMockFn(regenerateIds).mockReturnValue(new Map());
getMockFn(regenerateIdsWithReference).mockReturnValue(Promise.resolve(new Map()));
getMockFn(validateReferences).mockResolvedValue([]);
getMockFn(checkConflicts).mockResolvedValue({
errors: [],
Expand Down Expand Up @@ -240,6 +241,15 @@ describe('#importSavedObjectsFromStream', () => {
]),
});
getMockFn(validateReferences).mockResolvedValue([errors[1]]);
getMockFn(regenerateIdsWithReference).mockResolvedValue(
Promise.resolve(
new Map([
['foo', {}],
['bar', {}],
['baz', {}],
])
)
);
getMockFn(checkConflicts).mockResolvedValue({
errors: [errors[2]],
filteredObjects,
Expand Down
12 changes: 11 additions & 1 deletion src/core/server/saved_objects/import/import_saved_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { validateReferences } from './validate_references';
import { checkOriginConflicts } from './check_origin_conflicts';
import { createSavedObjects } from './create_saved_objects';
import { checkConflicts } from './check_conflicts';
import { regenerateIds } from './regenerate_ids';
import { regenerateIds, regenerateIdsWithReference } from './regenerate_ids';

/**
* Import saved objects from given stream. See the {@link SavedObjectsImportOptions | options} for more
Expand All @@ -54,6 +54,7 @@ export async function importSavedObjectsFromStream({
savedObjectsClient,
typeRegistry,
namespace,
workspaces,
}: SavedObjectsImportOptions): Promise<SavedObjectsImportResponse> {
let errorAccumulator: SavedObjectsImportError[] = [];
const supportedTypes = typeRegistry.getImportableAndExportableTypes().map((type) => type.name);
Expand All @@ -80,12 +81,20 @@ export async function importSavedObjectsFromStream({
if (createNewCopies) {
importIdMap = regenerateIds(collectSavedObjectsResult.collectedObjects);
} else {
importIdMap = await regenerateIdsWithReference({
savedObjects: collectSavedObjectsResult.collectedObjects,
savedObjectsClient,
workspaces,
objectLimit,
importIdMap,
});
// Check single-namespace objects for conflicts in this namespace, and check multi-namespace objects for conflicts across all namespaces
const checkConflictsParams = {
objects: collectSavedObjectsResult.collectedObjects,
savedObjectsClient,
namespace,
ignoreRegularConflicts: overwrite,
workspaces,
};
const checkConflictsResult = await checkConflicts(checkConflictsParams);
errorAccumulator = [...errorAccumulator, ...checkConflictsResult.errors];
Expand Down Expand Up @@ -118,6 +127,7 @@ export async function importSavedObjectsFromStream({
importIdMap,
overwrite,
namespace,
...(workspaces ? { workspaces } : {}),
};
const createSavedObjectsResult = await createSavedObjects(createSavedObjectsParams);
errorAccumulator = [...errorAccumulator, ...createSavedObjectsResult.errors];
Expand Down
40 changes: 39 additions & 1 deletion src/core/server/saved_objects/import/regenerate_ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
*/

import { v4 as uuidv4 } from 'uuid';
import { SavedObject } from '../types';
import { SavedObject, SavedObjectsClientContract } from '../types';
import { SavedObjectsUtils } from '../service';

/**
* Takes an array of saved objects and returns an importIdMap of randomly-generated new IDs.
Expand All @@ -42,3 +43,40 @@ export const regenerateIds = (objects: SavedObject[]) => {
}, new Map<string, { id: string; omitOriginId?: boolean }>());
return importIdMap;
};

export const regenerateIdsWithReference = async (props: {
savedObjects: SavedObject[];
savedObjectsClient: SavedObjectsClientContract;
workspaces?: string[];
objectLimit: number;
importIdMap: Map<string, { id?: string; omitOriginId?: boolean }>;
}): Promise<Map<string, { id?: string; omitOriginId?: boolean }>> => {
const { savedObjects, savedObjectsClient, workspaces, importIdMap } = props;
if (!workspaces || !workspaces.length) {
return savedObjects.reduce((acc, object) => {
return acc.set(`${object.type}:${object.id}`, { id: object.id, omitOriginId: false });
}, importIdMap);
}

const bulkGetResult = await savedObjectsClient.bulkGet(
savedObjects.map((item) => ({ type: item.type, id: item.id }))
);

return bulkGetResult.saved_objects.reduce((acc, object) => {
if (object.error?.statusCode === 404) {
acc.set(`${object.type}:${object.id}`, { id: object.id, omitOriginId: true });
return acc;
}

const filteredWorkspaces = SavedObjectsUtils.filterWorkspacesAccordingToBaseWorkspaces(
workspaces,
object.workspaces
);
if (filteredWorkspaces.length) {
acc.set(`${object.type}:${object.id}`, { id: uuidv4(), omitOriginId: true });
} else {
acc.set(`${object.type}:${object.id}`, { id: object.id, omitOriginId: false });
}
return acc;
}, importIdMap);
};
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export async function resolveSavedObjectsImportErrors({
typeRegistry,
namespace,
createNewCopies,
workspaces,
}: SavedObjectsResolveImportErrorsOptions): Promise<SavedObjectsImportResponse> {
// throw a BadRequest error if we see invalid retries
validateRetries(retries);
Expand Down Expand Up @@ -157,6 +158,7 @@ export async function resolveSavedObjectsImportErrors({
importIdMap,
namespace,
overwrite,
workspaces,
};
const { createdObjects, errors: bulkCreateErrors } = await createSavedObjects(
createSavedObjectsParams
Expand Down
4 changes: 4 additions & 0 deletions src/core/server/saved_objects/import/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ export interface SavedObjectsImportOptions {
namespace?: string;
/** If true, will create new copies of import objects, each with a random `id` and undefined `originId`. */
createNewCopies: boolean;
/** if specified, will import in given workspaces, else will import as global object */
workspaces?: string[];
}

/**
Expand All @@ -208,6 +210,8 @@ export interface SavedObjectsResolveImportErrorsOptions {
namespace?: string;
/** If true, will create new copies of import objects, each with a random `id` and undefined `originId`. */
createNewCopies: boolean;
/** if specified, will import in given workspaces, else will import as global object */
workspaces?: string[];
}

export type CreatedObject<T> = SavedObject<T> & { destinationId?: string };

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ function defaultMapping(): IndexMapping {
},
},
},
workspaces: {
type: 'keyword',
},
permissions: {
properties: {
read: principals,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe('IndexMigrator', () => {
references: '7997cf5a56cc02bdc9c93361bde732b0',
type: '2f4316de49999235636386fe51dc06c1',
updated_at: '00da57df13e94e9d98437d13ace4bfe0',
workspaces: '2f4316de49999235636386fe51dc06c1',
},
},
properties: {
Expand All @@ -93,6 +94,9 @@ describe('IndexMigrator', () => {
originId: { type: 'keyword' },
type: { type: 'keyword' },
updated_at: { type: 'date' },
workspaces: {
type: 'keyword',
},
permissions: {
properties: {
library_read: {
Expand Down Expand Up @@ -235,6 +239,7 @@ describe('IndexMigrator', () => {
references: '7997cf5a56cc02bdc9c93361bde732b0',
type: '2f4316de49999235636386fe51dc06c1',
updated_at: '00da57df13e94e9d98437d13ace4bfe0',
workspaces: '2f4316de49999235636386fe51dc06c1',
},
},
properties: {
Expand All @@ -246,6 +251,9 @@ describe('IndexMigrator', () => {
originId: { type: 'keyword' },
type: { type: 'keyword' },
updated_at: { type: 'date' },
workspaces: {
type: 'keyword',
},
permissions: {
properties: {
library_read: {
Expand Down Expand Up @@ -331,6 +339,7 @@ describe('IndexMigrator', () => {
references: '7997cf5a56cc02bdc9c93361bde732b0',
type: '2f4316de49999235636386fe51dc06c1',
updated_at: '00da57df13e94e9d98437d13ace4bfe0',
workspaces: '2f4316de49999235636386fe51dc06c1',
},
},
properties: {
Expand All @@ -342,6 +351,9 @@ describe('IndexMigrator', () => {
originId: { type: 'keyword' },
type: { type: 'keyword' },
updated_at: { type: 'date' },
workspaces: {
type: 'keyword',
},
permissions: {
properties: {
library_read: {
Expand Down
Loading
Loading