Skip to content

Commit

Permalink
Merge branch 'workspace' into feat-filter-features
Browse files Browse the repository at this point in the history
  • Loading branch information
raintygao authored Aug 25, 2023
2 parents b845fa0 + d384519 commit c0cc77e
Show file tree
Hide file tree
Showing 33 changed files with 691 additions and 316 deletions.

Large diffs are not rendered by default.

10 changes: 1 addition & 9 deletions src/core/public/chrome/ui/header/collapsible_nav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,6 @@ describe('CollapsibleNav', () => {
});

it('remembers collapsible section state', () => {
/**
* TODO skip for workspace refractor, will revert once refractor the left menu part
*/
return;
const navLinks = [
mockLink({ category: opensearchDashboards }),
mockLink({ category: observability }),
Expand All @@ -172,7 +168,7 @@ describe('CollapsibleNav', () => {
recentlyAccessed$={new BehaviorSubject(recentNavLinks)}
/>
);
expectShownNavLinksCount(component, 0);
expectShownNavLinksCount(component, 3);
clickGroup(component, 'opensearchDashboards');
clickGroup(component, 'recentlyViewed');
expectShownNavLinksCount(component, 1);
Expand All @@ -183,10 +179,6 @@ describe('CollapsibleNav', () => {
});

it('closes the nav after clicking a link', () => {
/**
* TODO skip for workspace refractor, will revert once refractor the left menu part
*/
return;
const onClose = sinon.spy();
const navLinks = [
mockLink({ category: opensearchDashboards }),
Expand Down
3 changes: 3 additions & 0 deletions src/core/public/workspace/workspaces_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import { WorkspaceAttribute } from '..';
const currentWorkspaceId$ = new BehaviorSubject<string>('');
const workspaceList$ = new BehaviorSubject<WorkspaceAttribute[]>([]);
const currentWorkspace$ = new BehaviorSubject<WorkspaceAttribute | null>(null);
const hasFetchedWorkspaceList$ = new BehaviorSubject<boolean>(false);
const workspaceEnabled$ = new BehaviorSubject<boolean>(false);

const createWorkspacesSetupContractMock = () => ({
currentWorkspaceId$,
workspaceList$,
currentWorkspace$,
hasFetchedWorkspaceList$,
workspaceEnabled$,
registerWorkspaceMenuRender: jest.fn(),
});
Expand All @@ -23,6 +25,7 @@ const createWorkspacesStartContractMock = () => ({
currentWorkspaceId$,
workspaceList$,
currentWorkspace$,
hasFetchedWorkspaceList$,
workspaceEnabled$,
renderWorkspaceMenu: jest.fn(),
});
Expand Down
5 changes: 5 additions & 0 deletions src/core/public/workspace/workspaces_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface WorkspaceObservables {
currentWorkspace$: BehaviorSubject<WorkspaceAttribute | null>;
workspaceList$: BehaviorSubject<WorkspaceAttribute[]>;
workspaceEnabled$: BehaviorSubject<boolean>;
hasFetchedWorkspaceList$: BehaviorSubject<boolean>;
}

/**
Expand All @@ -40,6 +41,7 @@ export class WorkspaceService implements CoreService<WorkspaceSetup, WorkspaceSt
private currentWorkspaceId$ = new BehaviorSubject<string>('');
private workspaceList$ = new BehaviorSubject<WorkspaceAttribute[]>([]);
private currentWorkspace$ = new BehaviorSubject<WorkspaceAttribute | null>(null);
private hasFetchedWorkspaceList$ = new BehaviorSubject<boolean>(false);
private workspaceEnabled$ = new BehaviorSubject<boolean>(false);
private _renderWorkspaceMenu: WorkspaceMenuRenderFn | null = null;

Expand All @@ -48,6 +50,7 @@ export class WorkspaceService implements CoreService<WorkspaceSetup, WorkspaceSt
currentWorkspaceId$: this.currentWorkspaceId$,
currentWorkspace$: this.currentWorkspace$,
workspaceList$: this.workspaceList$,
hasFetchedWorkspaceList$: this.hasFetchedWorkspaceList$,
workspaceEnabled$: this.workspaceEnabled$,
registerWorkspaceMenuRender: (render: WorkspaceMenuRenderFn) =>
(this._renderWorkspaceMenu = render),
Expand All @@ -65,6 +68,7 @@ export class WorkspaceService implements CoreService<WorkspaceSetup, WorkspaceSt
currentWorkspaceId$: this.currentWorkspaceId$,
currentWorkspace$: this.currentWorkspace$,
workspaceList$: this.workspaceList$,
hasFetchedWorkspaceList$: this.hasFetchedWorkspaceList$,
workspaceEnabled$: this.workspaceEnabled$,
};
return {
Expand All @@ -87,6 +91,7 @@ export class WorkspaceService implements CoreService<WorkspaceSetup, WorkspaceSt
this.currentWorkspaceId$.unsubscribe();
this.workspaceList$.unsubscribe();
this.workspaceEnabled$.unsubscribe();
this.hasFetchedWorkspaceList$.unsubscribe();
this._renderWorkspaceMenu = null;
}
}
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
9 changes: 8 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 Down Expand Up @@ -81,12 +81,19 @@ export async function importSavedObjectsFromStream({
if (createNewCopies) {
importIdMap = regenerateIds(collectSavedObjectsResult.collectedObjects);
} else {
importIdMap = await regenerateIdsWithReference({
savedObjects: collectSavedObjectsResult.collectedObjects,
savedObjectsClient,
workspaces,
objectLimit,
});
// 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
39 changes: 38 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,39 @@ 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;
}): Promise<Map<string, { id?: string; omitOriginId?: boolean }>> => {
const { savedObjects, savedObjectsClient, workspaces } = props;
if (!workspaces || !workspaces.length) {
return savedObjects.reduce((acc, object) => {
return acc.set(`${object.type}:${object.id}`, { id: object.id, omitOriginId: false });
}, new Map<string, { id: string; omitOriginId?: boolean }>());
}

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;
}, new Map<string, { id: string; omitOriginId?: boolean }>());
};
5 changes: 4 additions & 1 deletion src/core/server/saved_objects/permission_control/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ export class SavedObjectsPermissionControl {
if (savedObjectsGet) {
const principals = this.getPrincipalsFromRequest(request);
const hasAllPermission = savedObjectsGet.every((item) => {
// item.permissions
// for object that doesn't contain ACL like config, return true
if (!item.permissions) {
return true;
}
const aclInstance = new ACL(item.permissions);
return aclInstance.hasPermission(permissionModes, principals);
});
Expand Down
110 changes: 84 additions & 26 deletions src/core/server/saved_objects/service/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,15 +378,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;

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 @@ -397,7 +410,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 @@ -426,15 +439,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 @@ -471,12 +477,57 @@ export class SavedObjectsRepository {
versionProperties = getExpectedVersionProperties(version);
}

if (expectedBulkGetResult.value.method === 'create') {
savedObjectWorkspaces = options.workspaces;
} else {
const changeToCreate = () => {
finalMethod = 'create';
finalObjectId = object.id;
savedObjectWorkspaces = options.workspaces;
versionProperties = {};
};
/**
* When overwrite, need to check if the object is workspace-specific
* if so, copy object to target workspace instead of refering it.
*/
const rawId = this._serializer.generateRawId(namespace, object.type, object.id);
const findObject =
bulkGetResponse?.statusCode !== 404
? bulkGetResponse?.body.docs?.find((item) => item._id === rawId)
: null;
if (findObject && findObject.found) {
const transformedObject = this._serializer.rawToSavedObject(
findObject as SavedObjectsRawDoc
) as SavedObject;
const filteredWorkspaces = SavedObjectsUtils.filterWorkspacesAccordingToBaseWorkspaces(
options.workspaces,
transformedObject.workspaces
);
/**
* We need to create a new object when the object
* is about to import into workspaces it is not belong to
*/
if (filteredWorkspaces.length) {
/**
* Create a new object but only belong to the set of (target workspaces - original workspace)
*/
changeToCreate();
finalObjectId = uuid.v1();
savedObjectWorkspaces = filteredWorkspaces;
} else {
savedObjectWorkspaces = transformedObject.workspaces;
}
} else {
savedObjectWorkspaces = options.workspaces;
}
}

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 @@ -492,7 +543,7 @@ export class SavedObjectsRepository {

bulkCreateParams.push(
{
[method]: {
[finalMethod]: {
_id: expectedResult.rawMigratedDoc._id,
_index: this.getIndexForType(object.type),
...(overwrite && versionProperties),
Expand Down Expand Up @@ -583,7 +634,7 @@ export class SavedObjectsRepository {
const bulkGetDocs = expectedBulkGetResults.filter(isRight).map(({ value: { 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 All @@ -606,17 +657,24 @@ export class SavedObjectsRepository {
const { type, id, opensearchRequestIndex } = expectedResult.value;
const doc = bulkGetResponse?.body.docs[opensearchRequestIndex];
if (doc?.found) {
errors.push({
id,
type,
error: {
...errorContent(SavedObjectsErrorHelpers.createConflictError(type, id)),
// @ts-expect-error MultiGetHit._source is optional
...(!this.rawDocExistsInNamespace(doc!, namespace) && {
metadata: { isNotOverwritable: true },
}),
},
});
const transformedObject = this._serializer.rawToSavedObject(doc as SavedObjectsRawDoc);
const filteredWorkspaces = SavedObjectsUtils.filterWorkspacesAccordingToBaseWorkspaces(
options.workspaces,
transformedObject.workspaces
);
if (!filteredWorkspaces.length) {
errors.push({
id,
type,
error: {
...errorContent(SavedObjectsErrorHelpers.createConflictError(type, id)),
// @ts-expect-error MultiGetHit._source is optional
...(!this.rawDocExistsInNamespace(doc!, namespace) && {
metadata: { isNotOverwritable: true },
}),
},
});
}
}
});

Expand Down
7 changes: 7 additions & 0 deletions src/core/server/saved_objects/service/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,11 @@ export class SavedObjectsUtils {
total: 0,
saved_objects: [],
});

public static filterWorkspacesAccordingToBaseWorkspaces(
targetWorkspaces?: string[],
baseWorkspaces?: string[]
): string[] {
return targetWorkspaces?.filter((item) => !baseWorkspaces?.includes(item)) || [];
}
}
Loading

0 comments on commit c0cc77e

Please sign in to comment.