Skip to content

Commit

Permalink
fix: make importing workspace-specific objects as copy
Browse files Browse the repository at this point in the history
Signed-off-by: SuZhou-Joe <[email protected]>
  • Loading branch information
SuZhou-Joe committed Jul 26, 2023
1 parent 3f7a55a commit 5f01ef5
Show file tree
Hide file tree
Showing 15 changed files with 99 additions and 53 deletions.
8 changes: 2 additions & 6 deletions src/core/public/saved_objects/saved_objects_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface SavedObjectsCreateOptions {
/** {@inheritDoc SavedObjectsMigrationVersion} */
migrationVersion?: SavedObjectsMigrationVersion;
references?: SavedObjectReference[];
workspaces?: string[];
workspace?: string;
}

/**
Expand Down Expand Up @@ -268,11 +268,7 @@ export class SavedObjectsClient {
attributes,
migrationVersion: options.migrationVersion,
references: options.references,
...(options.workspaces || currentWorkspaceId
? {
workspaces: options.workspaces || [currentWorkspaceId],
}
: {}),
workspace: options.workspace || currentWorkspaceId,
}),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ 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, workspaces });
const bulkGetResult = await savedObjectsClient.bulkGet(objects, { namespace });
const erroredObjects = bulkGetResult.saved_objects.filter((obj) => !!obj.error);
if (erroredObjects.length) {
const err = Boom.badRequest();
Expand Down
6 changes: 3 additions & 3 deletions src/core/server/saved_objects/import/create_saved_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ interface CreateSavedObjectsParams<T> {
importIdMap: Map<string, { id?: string; omitOriginId?: boolean }>;
namespace?: string;
overwrite?: boolean;
workspaces?: string[];
workspace?: string;
}
interface CreateSavedObjectsResult<T> {
createdObjects: Array<CreatedObject<T>>;
Expand All @@ -57,7 +57,7 @@ export const createSavedObjects = async <T>({
importIdMap,
namespace,
overwrite,
workspaces,
workspace,
}: CreateSavedObjectsParams<T>): Promise<CreateSavedObjectsResult<T>> => {
// filter out any objects that resulted in errors
const errorSet = accumulatedErrors.reduce(
Expand Down Expand Up @@ -105,7 +105,7 @@ export const createSavedObjects = async <T>({
const bulkCreateResponse = await savedObjectsClient.bulkCreate(objectsToCreate, {
namespace,
overwrite,
workspaces,
workspace,
});
expectedResults = bulkCreateResponse.saved_objects;
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/server/saved_objects/import/import_saved_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function importSavedObjectsFromStream({
savedObjectsClient,
typeRegistry,
namespace,
workspaces,
workspace,
}: SavedObjectsImportOptions): Promise<SavedObjectsImportResponse> {
let errorAccumulator: SavedObjectsImportError[] = [];
const supportedTypes = typeRegistry.getImportableAndExportableTypes().map((type) => type.name);
Expand Down Expand Up @@ -119,7 +119,7 @@ export async function importSavedObjectsFromStream({
importIdMap,
overwrite,
namespace,
workspaces,
workspace,
};
const createSavedObjectsResult = await createSavedObjects(createSavedObjectsParams);
errorAccumulator = [...errorAccumulator, ...createSavedObjectsResult.errors];
Expand Down
4 changes: 2 additions & 2 deletions src/core/server/saved_objects/import/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +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[];
/** if specified, will import in given workspace, else will import as global object */
workspace?: string;
}

/**
Expand Down
8 changes: 3 additions & 5 deletions src/core/server/saved_objects/routes/bulk_create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@

import { schema } from '@osd/config-schema';
import { IRouter } from '../../http';
import { formatWorkspaces, workspacesValidator } from '../../workspaces';

export const registerBulkCreateRoute = (router: IRouter) => {
router.post(
Expand All @@ -39,7 +38,7 @@ export const registerBulkCreateRoute = (router: IRouter) => {
validate: {
query: schema.object({
overwrite: schema.boolean({ defaultValue: false }),
workspaces: workspacesValidator,
workspace: schema.maybe(schema.string()),
}),
body: schema.arrayOf(
schema.object({
Expand All @@ -63,11 +62,10 @@ export const registerBulkCreateRoute = (router: IRouter) => {
},
},
router.handleLegacyErrors(async (context, req, res) => {
const { overwrite } = req.query;
const workspaces = formatWorkspaces(req.query.workspaces);
const { overwrite, workspace } = req.query;
const result = await context.core.savedObjects.client.bulkCreate(req.body, {
overwrite,
workspaces,
workspace,
});
return res.ok({ body: result });
})
Expand Down
2 changes: 1 addition & 1 deletion src/core/server/saved_objects/routes/copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const registerCopyRoute = (router: IRouter, config: SavedObjectConfig) =>
objectLimit: maxImportExportSize,
overwrite: false,
createNewCopies: true,
workspaces: [targetWorkspace],
workspace: targetWorkspace,
});

return res.ok({ body: result });
Expand Down
6 changes: 3 additions & 3 deletions src/core/server/saved_objects/routes/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,22 @@ export const registerCreateRoute = (router: IRouter) => {
)
),
initialNamespaces: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })),
workspaces: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })),
workspace: schema.maybe(schema.string()),
}),
},
},
router.handleLegacyErrors(async (context, req, res) => {
const { type, id } = req.params;
const { overwrite } = req.query;
const { attributes, migrationVersion, references, initialNamespaces, workspaces } = req.body;
const { attributes, migrationVersion, references, initialNamespaces, workspace } = req.body;

const options = {
id,
overwrite,
migrationVersion,
references,
initialNamespaces,
workspaces,
workspace,
};
const result = await context.core.savedObjects.client.create(type, attributes, options);
return res.ok({ body: result });
Expand Down
9 changes: 3 additions & 6 deletions src/core/server/saved_objects/routes/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import { IRouter } from '../../http';
import { importSavedObjectsFromStream } from '../import';
import { SavedObjectConfig } from '../saved_objects_config';
import { createSavedObjectsStreamFromNdJson } from './utils';
import { formatWorkspaces, workspacesValidator } from '../../workspaces';

interface FileStream extends Readable {
hapi: {
Expand All @@ -61,7 +60,7 @@ export const registerImportRoute = (router: IRouter, config: SavedObjectConfig)
{
overwrite: schema.boolean({ defaultValue: false }),
createNewCopies: schema.boolean({ defaultValue: false }),
workspaces: workspacesValidator,
workspace: schema.maybe(schema.string()),
},
{
validate: (object) => {
Expand All @@ -77,7 +76,7 @@ export const registerImportRoute = (router: IRouter, config: SavedObjectConfig)
},
},
router.handleLegacyErrors(async (context, req, res) => {
const { overwrite, createNewCopies } = req.query;
const { overwrite, createNewCopies, workspace } = req.query;
const file = req.body.file as FileStream;
const fileExtension = extname(file.hapi.filename).toLowerCase();
if (fileExtension !== '.ndjson') {
Expand All @@ -93,16 +92,14 @@ export const registerImportRoute = (router: IRouter, config: SavedObjectConfig)
});
}

const workspaces = formatWorkspaces(req.query.workspaces);

const result = await importSavedObjectsFromStream({
savedObjectsClient: context.core.savedObjects.client,
typeRegistry: context.core.savedObjects.typeRegistry,
readStream,
objectLimit: maxImportExportSize,
overwrite,
createNewCopies,
workspaces,
workspace,
});

return res.ok({ body: result });
Expand Down
87 changes: 71 additions & 16 deletions src/core/server/saved_objects/service/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,18 @@ export class SavedObjectsRepository {
this._serializer = serializer;
}

private isSharedObject(object: SavedObject) {
if (!object.workspaces || object.workspaces.includes('public')) {
return true;
}

return false;
}

private isWorkspaceSpecificObject(object: SavedObject) {
return !this.isSharedObject(object);
}

/**
* Persists an object
*
Expand Down Expand Up @@ -244,7 +256,7 @@ export class SavedObjectsRepository {
originId,
initialNamespaces,
version,
workspaces,
workspace,
} = options;
const namespace = normalizeNamespace(options.namespace);

Expand Down Expand Up @@ -281,7 +293,7 @@ export class SavedObjectsRepository {
}
}

let savedObjectWorkspaces = workspaces;
let savedObjectWorkspaces = workspace ? [workspace] : undefined;

if (id && overwrite) {
try {
Expand Down Expand Up @@ -372,15 +384,28 @@ export class SavedObjectsRepository {

const method = object.id && overwrite ? 'index' : 'create';
const requiresNamespacesCheck = object.id && this._registry.isMultiNamespace(object.type);
/**
* Only when importing an object to a target workspace should we check if the object is workspace-specific.
*/
const requiresWorkspaceCheck = object.id && options.workspace;

if (object.id == null) object.id = uuid.v1();

let opensearchRequestIndexPayload = {};

if (requiresNamespacesCheck || requiresWorkspaceCheck) {
opensearchRequestIndexPayload = {
opensearchRequestIndex: bulkGetRequestIndexCounter,
};
bulkGetRequestIndexCounter++;
}

return {
tag: 'Right' as 'Right',
value: {
method,
object,
...(requiresNamespacesCheck && { opensearchRequestIndex: bulkGetRequestIndexCounter++ }),
...opensearchRequestIndexPayload,
},
};
});
Expand All @@ -391,7 +416,7 @@ export class SavedObjectsRepository {
.map(({ value: { object: { type, id } } }) => ({
_id: this._serializer.generateRawId(namespace, type, id),
_index: this.getIndexForType(type),
_source: ['type', 'namespaces'],
_source: ['type', 'namespaces', 'workspaces'],
}));
const bulkGetResponse = bulkGetDocs.length
? await this.client.mget(
Expand Down Expand Up @@ -420,15 +445,8 @@ export class SavedObjectsRepository {
method,
} = expectedBulkGetResult.value;
let savedObjectWorkspaces: string[] | undefined;
if (expectedBulkGetResult.value.method === 'create') {
if (options.workspaces) {
savedObjectWorkspaces = Array.from(new Set([...(options.workspaces || [])]));
}
} else if (object.workspaces) {
savedObjectWorkspaces = Array.from(
new Set([...object.workspaces, ...(options.workspaces || [])])
);
}
let finalMethod = method;
let finalObjectId = object.id;
if (opensearchRequestIndex !== undefined) {
const indexFound = bulkGetResponse?.statusCode !== 404;
const actualResult = indexFound
Expand Down Expand Up @@ -465,12 +483,49 @@ export class SavedObjectsRepository {
versionProperties = getExpectedVersionProperties(version);
}

if (expectedBulkGetResult.value.method === 'create') {
if (options.workspace) {
savedObjectWorkspaces = [options.workspace];
}
} else {
/**
* When overwrite, need to check if the object is workspace-specific
* if so, copy object to target workspace instead of refering it.
*/
if (opensearchRequestIndex !== undefined && bulkGetResponse?.statusCode !== 404) {
const rawId = this._serializer.generateRawId(namespace, object.type, object.id);
const findObject = bulkGetResponse?.body.docs?.find((item) => item._id === rawId);
if (findObject && findObject.found) {
const transformedObject = this._serializer.rawToSavedObject(
findObject as SavedObjectsRawDoc
) as SavedObject;
if (
options.workspace &&
this.isWorkspaceSpecificObject(transformedObject) &&
!transformedObject.workspaces?.includes(options.workspace)
) {
finalMethod = 'create';
finalObjectId = uuid.v1();
savedObjectWorkspaces = [options.workspace];
versionProperties = {};
} else {
savedObjectWorkspaces = transformedObject.workspaces;
}
} else {
finalMethod = 'create';
finalObjectId = object.id;
savedObjectWorkspaces = options.workspace ? [options.workspace] : undefined;
versionProperties = {};
}
}
}

const expectedResult = {
opensearchRequestIndex: bulkRequestIndexCounter++,
requestedId: object.id,
requestedId: finalObjectId,
rawMigratedDoc: this._serializer.savedObjectToRaw(
this._migrator.migrateDocument({
id: object.id,
id: finalObjectId,
type: object.type,
attributes: object.attributes,
migrationVersion: object.migrationVersion,
Expand All @@ -486,7 +541,7 @@ export class SavedObjectsRepository {

bulkCreateParams.push(
{
[method]: {
[finalMethod]: {
_id: expectedResult.rawMigratedDoc._id,
_index: this.getIndexForType(object.type),
...(overwrite && versionProperties),
Expand Down
2 changes: 1 addition & 1 deletion src/core/server/saved_objects/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export interface SavedObjectsFindOptions {
export interface SavedObjectsBaseOptions {
/** Specify the namespace for this operation */
namespace?: string;
workspaces?: string[];
workspace?: string;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ interface ImportResponse {
export async function importFile(
http: HttpStart,
file: File,
{ createNewCopies, overwrite, workspaces }: ImportMode
{ createNewCopies, overwrite, workspace }: ImportMode
) {
const formData = new FormData();
formData.append('file', file);
const query = createNewCopies ? { createNewCopies, workspaces } : { overwrite, workspaces };
const query = createNewCopies ? { createNewCopies, workspace } : { overwrite, workspace };
return await http.post<ImportResponse>('/api/saved_objects/_import', {
body: formData,
headers: {
Expand Down
Loading

0 comments on commit 5f01ef5

Please sign in to comment.